mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
dba25fa966
Summary: The fix entails making `AllocationTestModule.valid` an Objective-C atomic property and funneling access to the ivar via the synthesized property getter and setter. While the data race was present in test code, it would make it more difficult to spot more severe data races with the TSan. Also, getting rid of a data race is always good. ## Changelog: [iOS][Fixed] - Data race related to access of `AllocationTestModule.valid` Pull Request resolved: https://github.com/facebook/react-native/pull/45191 Test Plan: `RCTAllocationTests` will test the implementation of `AllocationTestModule`. Reviewed By: christophpurrer Differential Revision: D59155083 Pulled By: javache fbshipit-source-id: e3217cffd0801377a25f04bf8ed0b4e2d1d88498
215 lines
6.8 KiB
Objective-C
215 lines
6.8 KiB
Objective-C
/*
|
|
* 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 <XCTest/XCTest.h>
|
|
|
|
#import <RCTTest/RCTTestRunner.h>
|
|
#import <React/RCTBridge+Private.h>
|
|
#import <React/RCTBridge.h>
|
|
#import <React/RCTModuleMethod.h>
|
|
#import <React/RCTRootView.h>
|
|
|
|
@interface AllocationTestModule : NSObject <RCTBridgeModule, RCTInvalidating>
|
|
|
|
@property (atomic, assign, getter=isValid) BOOL valid;
|
|
|
|
@end
|
|
|
|
@implementation AllocationTestModule
|
|
|
|
RCT_EXPORT_MODULE();
|
|
|
|
- (instancetype)init
|
|
{
|
|
if ((self = [super init])) {
|
|
self.valid = YES;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)invalidate
|
|
{
|
|
self.valid = NO;
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(test
|
|
: (__unused NSString *)a
|
|
: (__unused NSNumber *)b
|
|
: (__unused RCTResponseSenderBlock)c
|
|
: (__unused RCTResponseErrorBlock)d)
|
|
{
|
|
}
|
|
|
|
@end
|
|
|
|
@interface RCTAllocationTests : XCTestCase
|
|
@end
|
|
|
|
@implementation RCTAllocationTests {
|
|
NSURL *_bundleURL;
|
|
}
|
|
|
|
- (void)setUp
|
|
{
|
|
[super setUp];
|
|
|
|
NSString *bundleContents =
|
|
@"var __fbBatchedBridge = {"
|
|
" callFunctionReturnFlushedQueue: function() { return null; },"
|
|
" invokeCallbackAndReturnFlushedQueue: function() { return null; },"
|
|
" flushedQueue: function() { return null; },"
|
|
"};";
|
|
|
|
NSURL *tempDir = [NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES];
|
|
[[NSFileManager defaultManager] createDirectoryAtURL:tempDir
|
|
withIntermediateDirectories:YES
|
|
attributes:nil
|
|
error:NULL];
|
|
NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString];
|
|
NSString *fileName = [NSString stringWithFormat:@"rctallocationtests-bundle-%@.js", guid];
|
|
|
|
_bundleURL = [tempDir URLByAppendingPathComponent:fileName];
|
|
NSError *saveError;
|
|
if (![bundleContents writeToURL:_bundleURL atomically:YES encoding:NSUTF8StringEncoding error:&saveError]) {
|
|
XCTFail(@"Failed to save test bundle to %@, error: %@", _bundleURL, saveError);
|
|
};
|
|
}
|
|
|
|
- (void)tearDown
|
|
{
|
|
[super tearDown];
|
|
|
|
[[NSFileManager defaultManager] removeItemAtURL:_bundleURL error:NULL];
|
|
}
|
|
|
|
- (void)testBridgeIsDeallocated
|
|
{
|
|
__weak RCTBridge *weakBridge;
|
|
@autoreleasepool {
|
|
RCTRootView *view = [[RCTRootView alloc] initWithBundleURL:_bundleURL
|
|
moduleName:@""
|
|
initialProperties:nil
|
|
launchOptions:nil];
|
|
weakBridge = view.bridge;
|
|
XCTAssertNotNil(weakBridge, @"RCTBridge should have been created");
|
|
(void)view;
|
|
}
|
|
|
|
XCTAssertNil(weakBridge, @"RCTBridge should have been deallocated");
|
|
}
|
|
|
|
- (void)testModulesAreInvalidated
|
|
{
|
|
AllocationTestModule *module = [AllocationTestModule new];
|
|
@autoreleasepool {
|
|
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL
|
|
moduleProvider:^{
|
|
return @[ module ];
|
|
}
|
|
launchOptions:nil];
|
|
XCTAssertTrue(module.isValid, @"AllocationTestModule should be valid");
|
|
(void)bridge;
|
|
}
|
|
|
|
RCT_RUN_RUNLOOP_WHILE(module.isValid)
|
|
XCTAssertFalse(module.isValid, @"AllocationTestModule should have been invalidated by the bridge");
|
|
}
|
|
|
|
- (void)testModulesAreDeallocated
|
|
{
|
|
__weak AllocationTestModule *weakModule;
|
|
@autoreleasepool {
|
|
AllocationTestModule *module = [AllocationTestModule new];
|
|
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL
|
|
moduleProvider:^{
|
|
return @[ module ];
|
|
}
|
|
launchOptions:nil];
|
|
XCTAssertNotNil(module, @"AllocationTestModule should have been created");
|
|
weakModule = module;
|
|
(void)bridge;
|
|
}
|
|
|
|
RCT_RUN_RUNLOOP_WHILE(weakModule)
|
|
XCTAssertNil(weakModule, @"AllocationTestModule should have been deallocated");
|
|
}
|
|
|
|
- (void)testModuleMethodsAreDeallocated
|
|
{
|
|
static RCTMethodInfo methodInfo = {
|
|
.objcName = "test:(NSString *)a :(nonnull NSNumber *)b :(RCTResponseSenderBlock)c :(RCTResponseErrorBlock)d",
|
|
.jsName = "",
|
|
.isSync = false};
|
|
|
|
__weak RCTModuleMethod *weakMethod;
|
|
@autoreleasepool {
|
|
__autoreleasing RCTModuleMethod *method =
|
|
[[RCTModuleMethod alloc] initWithExportedMethod:&methodInfo moduleClass:[AllocationTestModule class]];
|
|
XCTAssertNotNil(method, @"RCTModuleMethod should have been created");
|
|
weakMethod = method;
|
|
}
|
|
|
|
RCT_RUN_RUNLOOP_WHILE(weakMethod)
|
|
XCTAssertNil(weakMethod, @"RCTModuleMethod should have been deallocated");
|
|
}
|
|
|
|
- (void)testContentViewIsInvalidated
|
|
{
|
|
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL moduleProvider:nil launchOptions:nil];
|
|
__weak UIView *rootContentView;
|
|
@autoreleasepool {
|
|
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"" initialProperties:nil];
|
|
RCT_RUN_RUNLOOP_WHILE(!(rootContentView = [rootView valueForKey:@"contentView"]))
|
|
XCTAssertTrue(rootContentView.userInteractionEnabled, @"RCTContentView should be valid");
|
|
(void)rootView;
|
|
}
|
|
|
|
#if !TARGET_OS_TV // userInteractionEnabled is true for Apple TV views
|
|
XCTAssertFalse(rootContentView.userInteractionEnabled, @"RCTContentView should have been invalidated");
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* T42930872:
|
|
*
|
|
* Both bridge invalidation and bridge setUp occur execute concurrently.
|
|
* Therefore, it's not safe for us to create a bridge, and immediately reload on
|
|
* it. It's also unsafe to just reload the bridge, because that calls invalidate
|
|
* and then setUp. Because of these race conditions, this test may randomly
|
|
* crash. Hence, we should disable this test until we either fix the bridge
|
|
* or delete it.
|
|
*/
|
|
- (void)disabled_testUnderlyingBridgeIsDeallocated
|
|
{
|
|
RCTBridge *bridge;
|
|
__weak id batchedBridge;
|
|
@autoreleasepool {
|
|
bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL moduleProvider:nil launchOptions:nil];
|
|
batchedBridge = bridge.batchedBridge;
|
|
XCTAssertTrue([batchedBridge isValid], @"RCTBridge impl should be valid");
|
|
[bridge reload];
|
|
}
|
|
|
|
RCT_RUN_RUNLOOP_WHILE(batchedBridge != nil)
|
|
|
|
XCTAssertNotNil(bridge, @"RCTBridge should not have been deallocated");
|
|
XCTAssertNil(batchedBridge, @"RCTBridge impl should have been deallocated");
|
|
|
|
// Wait to complete the test until the new bridge impl is also deallocated
|
|
@autoreleasepool {
|
|
batchedBridge = bridge.batchedBridge;
|
|
[bridge invalidate];
|
|
bridge = nil;
|
|
}
|
|
|
|
RCT_RUN_RUNLOOP_WHILE(batchedBridge != nil);
|
|
XCTAssertNil(batchedBridge);
|
|
}
|
|
|
|
@end
|