Added unit tests for module init

Summary: The module initialization process is complex and full of race conditions. This diff adds a set of unit tests that verify that modules setup happens in the correct order, and enforces all the various conditions for main/background init.

Reviewed By: javache

Differential Revision: D2994145

fb-gh-sync-id: 92ea84508cdeeb280ff0fb9e9b2dffa8dbc37e66
shipit-source-id: 92ea84508cdeeb280ff0fb9e9b2dffa8dbc37e66
This commit is contained in:
Nick Lockwood 2016-03-01 09:44:05 -08:00 committed by Facebook Github Bot 5
parent 0db22f184d
commit 35da174339
10 changed files with 452 additions and 29 deletions

View File

@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
1300627F1B59179B0043FE5A /* RCTGzipTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1300627E1B59179B0043FE5A /* RCTGzipTests.m */; };
13129DD41C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13129DD31C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m */; };
1323F1891C04AB9F0091BED0 /* bunny.png in Resources */ = {isa = PBXBuildFile; fileRef = 1323F1851C04AB9F0091BED0 /* bunny.png */; };
1323F18A1C04AB9F0091BED0 /* flux@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1323F1861C04AB9F0091BED0 /* flux@3x.png */; };
1323F18B1C04AB9F0091BED0 /* hawk.png in Resources */ = {isa = PBXBuildFile; fileRef = 1323F1871C04AB9F0091BED0 /* hawk.png */; };
@ -18,6 +19,7 @@
1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; };
134454601AAFCABD003F0779 /* libRCTAdSupport.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1344545A1AAFCAAE003F0779 /* libRCTAdSupport.a */; };
134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */; };
134CB92A1C85A38800265FA6 /* RCTModuleInitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 134CB9291C85A38800265FA6 /* RCTModuleInitTests.m */; };
138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A161B53CD440074A87E /* RCTShadowViewTests.m */; };
138DEE241B9EDFB6007F4EA5 /* libRCTCameraRoll.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 138DEE091B9EDDDB007F4EA5 /* libRCTCameraRoll.a */; };
1393D0381B68CD1300E1B601 /* RCTModuleMethodTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */; };
@ -178,6 +180,7 @@
/* Begin PBXFileReference section */
004D289E1AAF61C70097A701 /* UIExplorerUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIExplorerUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
1300627E1B59179B0043FE5A /* RCTGzipTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGzipTests.m; sourceTree = "<group>"; };
13129DD31C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleInitNotificationRaceTests.m; sourceTree = "<group>"; };
1323F1851C04AB9F0091BED0 /* bunny.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = bunny.png; sourceTree = "<group>"; };
1323F1861C04AB9F0091BED0 /* flux@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "flux@3x.png"; sourceTree = "<group>"; };
1323F1871C04AB9F0091BED0 /* hawk.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = hawk.png; sourceTree = "<group>"; };
@ -188,6 +191,7 @@
134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = "<group>"; };
134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAdSupport.xcodeproj; path = ../../Libraries/AdSupport/RCTAdSupport.xcodeproj; sourceTree = "<group>"; };
134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = "<group>"; };
134CB9291C85A38800265FA6 /* RCTModuleInitTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleInitTests.m; sourceTree = "<group>"; };
138D6A161B53CD440074A87E /* RCTShadowViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowViewTests.m; sourceTree = "<group>"; };
138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTCameraRoll.xcodeproj; path = ../../Libraries/CameraRoll/RCTCameraRoll.xcodeproj; sourceTree = "<group>"; };
1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMethodTests.m; sourceTree = "<group>"; };
@ -422,6 +426,8 @@
144D21231B2204C5006DB32B /* RCTImageUtilTests.m */,
13DB03471B5D2ED500C27245 /* RCTJSONTests.m */,
13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */,
134CB9291C85A38800265FA6 /* RCTModuleInitTests.m */,
13129DD31C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m */,
1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */,
138D6A161B53CD440074A87E /* RCTShadowViewTests.m */,
1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */,
@ -882,7 +888,9 @@
1300627F1B59179B0043FE5A /* RCTGzipTests.m in Sources */,
1497CFAF1B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m in Sources */,
1497CFAE1B21F5E400C1F8F2 /* RCTJSCExecutorTests.m in Sources */,
13129DD41C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m in Sources */,
1497CFAD1B21F5E400C1F8F2 /* RCTBridgeTests.m in Sources */,
134CB92A1C85A38800265FA6 /* RCTModuleInitTests.m in Sources */,
1497CFB11B21F5E400C1F8F2 /* RCTEventDispatcherTests.m in Sources */,
1497CFB31B21F5E400C1F8F2 /* RCTUIManagerTests.m in Sources */,
13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */,
@ -1276,4 +1284,4 @@
/* End XCConfigurationList section */
};
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
}
}

View File

@ -41,6 +41,8 @@
@implementation TestExecutor
@synthesize valid = _valid;
RCT_EXPORT_MODULE()
- (void)setUp {}
@ -55,7 +57,7 @@ RCT_EXPORT_MODULE()
- (BOOL)isValid
{
return YES;
return _valid;
}
- (void)flushedQueue:(RCTJavaScriptCallback)onComplete
@ -98,7 +100,10 @@ RCT_EXPORT_MODULE()
onComplete(nil);
}
- (void)invalidate {}
- (void)invalidate
{
_valid = NO;
}
@end
@ -132,7 +137,7 @@ RCT_EXPORT_MODULE(TestModule)
[_bridge invalidate];
[_bridge setUp];
_jsExecutor = [_bridge.batchedBridge valueForKey:@"javaScriptExecutor"];
_jsExecutor = _bridge.batchedBridge.javaScriptExecutor;
XCTAssertNotNil(_jsExecutor);
}
@ -143,6 +148,7 @@ RCT_EXPORT_MODULE(TestModule)
_testMethodCalled = NO;
[_bridge invalidate];
RUN_RUNLOOP_WHILE(_jsExecutor.isValid);
_bridge = nil;
}

View File

@ -0,0 +1,142 @@
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>
#import "RCTBridge.h"
#import "RCTBridge+Private.h"
#import "RCTBridgeModule.h"
#import "RCTUtils.h"
#import "RCTUIManager.h"
#import "RCTViewManager.h"
#define RUN_RUNLOOP_WHILE(CONDITION) \
{ \
NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:5]; \
while ((CONDITION)) { \
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; \
if ([timeout timeIntervalSinceNow] <= 0) { \
XCTFail(@"Runloop timed out before condition was met"); \
break; \
} \
} \
}
// Must be declared before RCTTestCustomSetBridgeModule in order to trigger the
// race condition that we are testing for - namely that the
// RCTDidInitializeModuleNotification for RCTTestViewManager gets sent before
// setBridge: is called on RCTTestCustomSetBridgeModule
@interface RCTTestViewManager : RCTViewManager
@end
@implementation RCTTestViewManager
@synthesize bridge = _bridge;
@synthesize methodQueue = _methodQueue;
RCT_EXPORT_MODULE()
- (void)setBridge:(RCTBridge *)bridge
{
_bridge = bridge;
(void)[_bridge uiManager]; // Needed to trigger a race condition
}
- (NSArray<NSString *> *)customDirectEventTypes
{
return @[@"foo"];
}
@end
@interface RCTNotificationObserverModule : NSObject <RCTBridgeModule>
@property (nonatomic, assign) BOOL didDetectViewManagerInit;
@end
@implementation RCTNotificationObserverModule
@synthesize bridge = _bridge;
RCT_EXPORT_MODULE()
- (void)setBridge:(RCTBridge *)bridge
{
_bridge = bridge;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didInitViewManager:) name:RCTDidInitializeModuleNotification object:nil];
}
- (void)didInitViewManager:(NSNotification *)note
{
id<RCTBridgeModule> module = note.userInfo[@"module"];
if ([module isKindOfClass:[RCTTestViewManager class]]) {
_didDetectViewManagerInit = YES;
}
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
@end
@interface RCTModuleInitNotificationRaceTests : XCTestCase <RCTBridgeDelegate>
{
RCTBridge *_bridge;
RCTNotificationObserverModule *_notificationObserver;
}
@end
@implementation RCTModuleInitNotificationRaceTests
- (NSURL *)sourceURLForBridge:(__unused RCTBridge *)bridge
{
return nil;
}
- (NSArray *)extraModulesForBridge:(__unused RCTBridge *)bridge
{
return @[_notificationObserver];
}
- (void)setUp
{
[super setUp];
_notificationObserver = [RCTNotificationObserverModule new];
_bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:nil];
}
- (void)tearDown
{
[super tearDown];
_notificationObserver = nil;
id<RCTJavaScriptExecutor> jsExecutor = _bridge.batchedBridge.javaScriptExecutor;
[_bridge invalidate];
RUN_RUNLOOP_WHILE(jsExecutor.isValid);
_bridge = nil;
}
- (void)testViewManagerNotInitializedBeforeSetBridgeModule
{
RUN_RUNLOOP_WHILE(!_notificationObserver.didDetectViewManagerInit);
}
@end

View File

@ -0,0 +1,254 @@
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>
#import "RCTBridge.h"
#import "RCTBridge+Private.h"
#import "RCTBridgeModule.h"
#import "RCTUtils.h"
#define RUN_RUNLOOP_WHILE(CONDITION) \
{ \
NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:5]; \
while ((CONDITION)) { \
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; \
if ([timeout timeIntervalSinceNow] <= 0) { \
XCTFail(@"Runloop timed out before condition was met"); \
break; \
} \
} \
}
@interface RCTTestInjectedModule : NSObject <RCTBridgeModule>
@end
@implementation RCTTestInjectedModule
@synthesize bridge = _bridge;
@synthesize methodQueue = _methodQueue;
RCT_EXPORT_MODULE()
@end
@interface RCTTestCustomInitModule : NSObject <RCTBridgeModule>
@property (nonatomic, assign) BOOL initializedOnMainThread;
@end
@implementation RCTTestCustomInitModule
@synthesize bridge = _bridge;
@synthesize methodQueue = _methodQueue;
RCT_EXPORT_MODULE()
- (id)init
{
if ((self = [super init])) {
_initializedOnMainThread = [NSThread isMainThread];
}
return self;
}
@end
@interface RCTTestCustomSetBridgeModule : NSObject <RCTBridgeModule>
@property (nonatomic, assign) BOOL setBridgeOnMainThread;
@end
@implementation RCTTestCustomSetBridgeModule
@synthesize bridge = _bridge;
@synthesize methodQueue = _methodQueue;
RCT_EXPORT_MODULE()
- (void)setBridge:(RCTBridge *)bridge
{
_bridge = bridge;
_setBridgeOnMainThread = [NSThread isMainThread];
}
@end
@interface RCTTestExportConstantsModule : NSObject <RCTBridgeModule>
@property (nonatomic, assign) BOOL exportedConstants;
@property (nonatomic, assign) BOOL exportedConstantsOnMainThread;
@end
@implementation RCTTestExportConstantsModule
@synthesize bridge = _bridge;
@synthesize methodQueue = _methodQueue;
RCT_EXPORT_MODULE()
- (NSDictionary<NSString *, id> *)constantsToExport
{
_exportedConstants = YES;
_exportedConstantsOnMainThread = [NSThread isMainThread];
return @{ @"foo": @"bar" };
}
@end
@interface RCTLazyInitModule : NSObject <RCTBridgeModule>
@end
@implementation RCTLazyInitModule
@synthesize bridge = _bridge;
@synthesize methodQueue = _methodQueue;
RCT_EXPORT_MODULE()
@end
@interface RCTModuleInitTests : XCTestCase <RCTBridgeDelegate>
{
RCTBridge *_bridge;
BOOL _injectedModuleInitNotificationSent;
BOOL _customInitModuleNotificationSent;
BOOL _customSetBridgeModuleNotificationSent;
BOOL _exportConstantsModuleNotificationSent;
BOOL _lazyInitModuleNotificationSent;
BOOL _lazyInitModuleNotificationSentOnMainThread;
BOOL _viewManagerModuleNotificationSent;
RCTTestInjectedModule *_injectedModule;
}
@end
@implementation RCTModuleInitTests
- (NSURL *)sourceURLForBridge:(__unused RCTBridge *)bridge
{
return nil;
}
- (NSArray *)extraModulesForBridge:(__unused RCTBridge *)bridge
{
return @[_injectedModule];
}
- (void)setUp
{
[super setUp];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moduleDidInit:) name:RCTDidInitializeModuleNotification object:nil];
_injectedModuleInitNotificationSent = NO;
_customInitModuleNotificationSent = NO;
_customSetBridgeModuleNotificationSent = NO;
_exportConstantsModuleNotificationSent = NO;
_lazyInitModuleNotificationSent = NO;
_viewManagerModuleNotificationSent = NO;
_injectedModule = [RCTTestInjectedModule new];
_bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:nil];
}
- (void)tearDown
{
[super tearDown];
[[NSNotificationCenter defaultCenter] removeObserver:self name:RCTDidInitializeModuleNotification object:nil];
id<RCTJavaScriptExecutor> jsExecutor = _bridge.batchedBridge.javaScriptExecutor;
[_bridge invalidate];
RUN_RUNLOOP_WHILE(jsExecutor.isValid);
_bridge = nil;
}
- (void)moduleDidInit:(NSNotification *)note
{
id<RCTBridgeModule> module = note.userInfo[@"module"];
if ([module isKindOfClass:[RCTTestInjectedModule class]]) {
_injectedModuleInitNotificationSent = YES;
} else if ([module isKindOfClass:[RCTTestCustomInitModule class]]) {
_customInitModuleNotificationSent = YES;
} else if ([module isKindOfClass:[RCTTestCustomSetBridgeModule class]]) {
_customSetBridgeModuleNotificationSent = YES;
} else if ([module isKindOfClass:[RCTTestExportConstantsModule class]]) {
_exportConstantsModuleNotificationSent = YES;
} else if ([module isKindOfClass:[RCTLazyInitModule class]]) {
_lazyInitModuleNotificationSent = YES;
_lazyInitModuleNotificationSentOnMainThread = [NSThread isMainThread];
}
}
- (void)testInjectedModulesInitializedDuringBridgeInit
{
XCTAssertTrue(_injectedModuleInitNotificationSent);
XCTAssertEqual(_injectedModule, [_bridge moduleForClass:[RCTTestInjectedModule class]]);
XCTAssertEqual(_injectedModule.bridge, _bridge.batchedBridge);
XCTAssertNotNil(_injectedModule.methodQueue);
}
- (void)testCustomInitModuleInitializedAtBridgeStartup
{
RUN_RUNLOOP_WHILE(!_customInitModuleNotificationSent);
XCTAssertTrue(_customInitModuleNotificationSent);
RCTTestCustomInitModule *module = [_bridge moduleForClass:[RCTTestCustomInitModule class]];
XCTAssertTrue(module.initializedOnMainThread);
}
- (void)testCustomSetBridgeModuleInitializedAtBridgeStartup
{
RUN_RUNLOOP_WHILE(!_customSetBridgeModuleNotificationSent);
XCTAssertTrue(_customSetBridgeModuleNotificationSent);
RCTTestCustomSetBridgeModule *module = [_bridge moduleForClass:[RCTTestCustomSetBridgeModule class]];
XCTAssertTrue(module.setBridgeOnMainThread);
}
- (void)testExportConstantsModuleInitializedAtBridgeStartup
{
RUN_RUNLOOP_WHILE(!_exportConstantsModuleNotificationSent);
XCTAssertTrue(_exportConstantsModuleNotificationSent);
RCTTestExportConstantsModule *module = [_bridge moduleForClass:[RCTTestExportConstantsModule class]];
RUN_RUNLOOP_WHILE(!module.exportedConstants);
XCTAssertTrue(module.exportedConstants);
XCTAssertTrue(module.exportedConstantsOnMainThread);
}
- (void)testLazyInitModuleNotInitializedDuringBridgeInit
{
XCTAssertFalse(_lazyInitModuleNotificationSent);
__block RCTLazyInitModule *module;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
module = [_bridge moduleForClass:[RCTLazyInitModule class]];
});
RUN_RUNLOOP_WHILE(!module);
XCTAssertTrue(_lazyInitModuleNotificationSent);
XCTAssertFalse(_lazyInitModuleNotificationSentOnMainThread);
XCTAssertNotNil(module);
XCTAssertEqual(module.bridge, _bridge.batchedBridge);
XCTAssertNotNil(module.methodQueue);
}
@end

View File

@ -44,6 +44,7 @@ RCT_EXTERN NSArray<Class> *RCTGetModuleClasses(void);
@interface RCTBatchedBridge : RCTBridge
@property (nonatomic, weak) RCTBridge *parentBridge;
@property (nonatomic, weak) id<RCTJavaScriptExecutor> javaScriptExecutor;
@end
@ -52,7 +53,6 @@ RCT_EXTERN NSArray<Class> *RCTGetModuleClasses(void);
BOOL _loading;
BOOL _valid;
BOOL _wasBatchActive;
__weak id<RCTJavaScriptExecutor> _javaScriptExecutor;
NSMutableArray<dispatch_block_t> *_pendingCalls;
NSMutableDictionary<NSString *, RCTModuleData *> *_moduleDataByName;
NSArray<RCTModuleData *> *_moduleDataByID;

View File

@ -54,6 +54,11 @@
@interface RCTBridge (RCTBatchedBridge)
/**
* Used for unit testing, to detect when executor has been invalidated.
*/
@property (nonatomic, weak, readonly) id<RCTJavaScriptExecutor> javaScriptExecutor;
/**
* Used by RCTModuleData to register the module for frame updates after it is
* lazily initialized.

View File

@ -68,7 +68,8 @@ void RCTRegisterModule(Class moduleClass)
NSString *RCTBridgeModuleNameForClass(Class cls)
{
#if RCT_DEV
RCTAssert([cls conformsToProtocol:@protocol(RCTBridgeModule)], @"Bridge module classes must conform to RCTBridgeModule");
RCTAssert([cls conformsToProtocol:@protocol(RCTBridgeModule)],
@"Bridge module `%@` does not conform to RCTBridgeModule", cls);
#endif
NSString *name = [cls moduleName];
@ -104,34 +105,33 @@ dispatch_queue_t RCTJSThread;
// Set up JS thread
RCTJSThread = (id)kCFNull;
#if RCT_DEBUG
if (RCT_DEBUG && !RCTRunningInTestEnvironment()) {
// Set up module classes
static unsigned int classCount;
Class *classes = objc_copyClassList(&classCount);
// Set up module classes
static unsigned int classCount;
Class *classes = objc_copyClassList(&classCount);
for (unsigned int i = 0; i < classCount; i++)
{
Class cls = classes[i];
Class superclass = cls;
while (superclass)
for (unsigned int i = 0; i < classCount; i++)
{
if (class_conformsToProtocol(superclass, @protocol(RCTBridgeModule)))
Class cls = classes[i];
Class superclass = cls;
while (superclass)
{
if (![RCTModuleClasses containsObject:cls]) {
RCTLogWarn(@"Class %@ was not exported. Did you forget to use "
"RCT_EXPORT_MODULE()?", cls);
if (class_conformsToProtocol(superclass, @protocol(RCTBridgeModule)))
{
if (![RCTModuleClasses containsObject:cls]) {
RCTLogWarn(@"Class %@ was not exported. Did you forget to use "
"RCT_EXPORT_MODULE()?", cls);
}
break;
}
break;
superclass = class_getSuperclass(superclass);
}
superclass = class_getSuperclass(superclass);
}
free(classes);
}
free(classes);
#endif
});
}

View File

@ -16,6 +16,7 @@
#import "RCTBridge+Private.h"
#import "RCTDefines.h"
#import "RCTRedBox.h"
#import "RCTUtils.h"
static NSString *const RCTLogFunctionStack = @"RCTLogFunctionStack";
@ -226,8 +227,10 @@ void _RCTLogNativeInternal(RCTLogLevel level, const char *fileName, int lineNumb
});
}
// Log to JS executor
[[RCTBridge currentBridge] logMessage:message level:level ? @(RCTLogLevels[level]) : @"info"];
if (!RCTRunningInTestEnvironment()) {
// Log to JS executor
[[RCTBridge currentBridge] logMessage:message level:level ? @(RCTLogLevels[level]) : @"info"];
}
#endif

View File

@ -238,4 +238,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init);
_methodQueue = nil;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p; name=\"%@\">", [self class], self, self.name];
}
@end

View File

@ -369,7 +369,7 @@ RCT_EXPORT_MODULE()
if (!sourceCodeModule.scriptURL) {
if (!sourceCodeModule) {
RCTLogWarn(@"RCTSourceCode module not found");
} else {
} else if (!RCTRunningInTestEnvironment()) {
RCTLogWarn(@"RCTSourceCode module scriptURL has not been set");
}
} else if (!sourceCodeModule.scriptURL.fileURL) {