Make several improvements to how tests run under RN
These changes include timeouts when waiting on notifications.
This commit is contained in:
parent
23a7c5b88d
commit
1f030a0618
|
@ -33,8 +33,7 @@ static NSString * const RJSModuleLoaderErrorDomain = @"RJSModuleLoaderErrorDomai
|
|||
self.globalModules[name] = [JSValue valueWithObject:object inContext:self.context];
|
||||
}
|
||||
|
||||
- (JSValue *)loadModule:(NSString *)name relativeToURL:(NSURL *)baseURL error:(NSError **)error
|
||||
{
|
||||
- (JSValue *)loadModule:(NSString *)name relativeToURL:(NSURL *)baseURL error:(NSError **)error {
|
||||
if (![name hasPrefix:@"./"] && ![name hasPrefix:@"../"]) {
|
||||
return [self loadGlobalModule:name relativeToURL:baseURL error:error];
|
||||
}
|
||||
|
|
|
@ -26,23 +26,15 @@
|
|||
[moduleLoader addGlobalModuleObject:realmConstructor forName:@"realm"];
|
||||
|
||||
NSError *error;
|
||||
JSValue *testObjects = [moduleLoader loadModuleFromURL:scriptURL error:&error];
|
||||
JSValue *testObject = [moduleLoader loadModuleFromURL:scriptURL error:&error];
|
||||
NSAssert(testObject, @"%@", error);
|
||||
|
||||
if (!testObjects) {
|
||||
NSLog(@"%@", error);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
NSDictionary *testCaseNames = [[testObjects invokeMethod:@"getTestNames" withArguments:nil] toDictionary];
|
||||
|
||||
if (!testCaseNames.count) {
|
||||
NSLog(@"No test case names from getTestNames() JS method!");
|
||||
exit(1);
|
||||
}
|
||||
NSDictionary *testCaseNames = [[testObject invokeMethod:@"getTestNames" withArguments:nil] toDictionary];
|
||||
NSAssert(testCaseNames.count, @"No test names were provided by the JS");
|
||||
|
||||
for (XCTestSuite *testSuite in [self testSuitesFromDictionary:testCaseNames]) {
|
||||
for (RealmJSCoreTests *test in testSuite.tests) {
|
||||
test.testObject = testObjects[testSuite.name];
|
||||
test.testObject = testObject;
|
||||
}
|
||||
|
||||
[suite addTest:testSuite];
|
||||
|
@ -57,15 +49,10 @@
|
|||
|
||||
- (void)invokeMethod:(NSString *)method {
|
||||
JSValue *testObject = self.testObject;
|
||||
|
||||
if (![testObject hasProperty:method]) {
|
||||
return;
|
||||
}
|
||||
|
||||
JSContext *context = testObject.context;
|
||||
context.exception = nil;
|
||||
|
||||
[testObject invokeMethod:method withArguments:nil];
|
||||
[testObject invokeMethod:@"runTest" withArguments:@[NSStringFromClass(self.class), method]];
|
||||
|
||||
JSValue *exception = context.exception;
|
||||
if (exception) {
|
||||
|
|
|
@ -10,12 +10,17 @@
|
|||
|
||||
+ (NSArray *)testSuitesFromDictionary:(NSDictionary *)testCaseNames {
|
||||
NSMutableArray *testSuites = [[NSMutableArray alloc] init];
|
||||
NSSet *specialNames = [NSSet setWithObjects:@"beforeEach", @"afterEach", nil];
|
||||
|
||||
for (NSString *suiteName in testCaseNames) {
|
||||
XCTestSuite *testSuite = [[XCTestSuite alloc] initWithName:suiteName];
|
||||
Class testClass = objc_allocateClassPair(self, suiteName.UTF8String, 0);
|
||||
|
||||
for (NSString *testName in testCaseNames[suiteName]) {
|
||||
if ([specialNames containsObject:testName]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
XCTestCase *testCase = [[testClass alloc] initWithTestName:testName];
|
||||
[testSuite addTest:testCase];
|
||||
}
|
||||
|
|
|
@ -14,11 +14,6 @@ exports.extend = function(object) {
|
|||
};
|
||||
|
||||
Object.defineProperties(prototype, {
|
||||
// TODO: Remove once missing undefined check is fixed inside RCTContextExecutor.
|
||||
beforeEach: {
|
||||
value: function() {}
|
||||
},
|
||||
|
||||
afterEach: {
|
||||
value: function() {
|
||||
Realm.clearTestState();
|
||||
|
|
|
@ -4,37 +4,40 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
exports.ListTests = require('./list-tests');
|
||||
exports.ObjectTests = require('./object-tests');
|
||||
exports.RealmTests = require('./realm-tests');
|
||||
exports.ResultsTests = require('./results-tests');
|
||||
exports.QueryTests = require('./query-tests');
|
||||
var TESTS = {
|
||||
ListTests: require('./list-tests'),
|
||||
ObjectTests: require('./object-tests'),
|
||||
RealmTests: require('./realm-tests'),
|
||||
ResultsTests: require('./results-tests'),
|
||||
QueryTests: require('./query-tests'),
|
||||
};
|
||||
|
||||
var SPECIAL_METHODS = {
|
||||
beforeEach: true,
|
||||
afterEach: true,
|
||||
};
|
||||
|
||||
// Only the test suites should be iterable members of exports.
|
||||
Object.defineProperties(exports, {
|
||||
getTestNames: {
|
||||
value: function() {
|
||||
var testNames = {};
|
||||
exports.getTestNames = function() {
|
||||
var testNames = {};
|
||||
|
||||
for (var suiteName in exports) {
|
||||
var testSuite = exports[suiteName];
|
||||
for (var suiteName in TESTS) {
|
||||
var testSuite = TESTS[suiteName];
|
||||
|
||||
testNames[suiteName] = Object.keys(testSuite).filter(function(testName) {
|
||||
return !(testName in SPECIAL_METHODS) && typeof testSuite[testName] == 'function';
|
||||
});
|
||||
}
|
||||
testNames[suiteName] = Object.keys(testSuite).filter(function(testName) {
|
||||
return !(testName in SPECIAL_METHODS) && typeof testSuite[testName] == 'function';
|
||||
});
|
||||
}
|
||||
|
||||
return testNames;
|
||||
}
|
||||
},
|
||||
runTest: {
|
||||
value: function(suiteName, testName) {
|
||||
exports[suiteName][testName]();
|
||||
}
|
||||
},
|
||||
});
|
||||
return testNames;
|
||||
};
|
||||
|
||||
exports.runTest = function(suiteName, testName) {
|
||||
var testSuite = TESTS[suiteName];
|
||||
var testMethod = testSuite && testSuite[testName];
|
||||
|
||||
if (testMethod) {
|
||||
testMethod.call(testSuite);
|
||||
} else if (!testSuite || !(testName in SPECIAL_METHODS)) {
|
||||
throw new Error('Missing test: ' + suiteName + '.' + testName);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
#import "RCTBridge.h"
|
||||
#import "RCTDevMenu.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTJavaScriptLoader.h"
|
||||
#import "RCTLog.h"
|
||||
|
||||
@import RealmReact;
|
||||
|
||||
|
@ -29,6 +31,10 @@ extern NSMutableArray *RCTGetModuleClasses(void);
|
|||
+ (void)load {
|
||||
NSMutableArray *moduleClasses = RCTGetModuleClasses();
|
||||
[moduleClasses removeObject:[RCTDevMenu class]];
|
||||
|
||||
RCTAddLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
|
||||
NSAssert(level < RCTLogLevelError, RCTFormatLog(nil, level, fileName, lineNumber, message));
|
||||
});
|
||||
}
|
||||
|
||||
+ (Class)executorClass {
|
||||
|
@ -84,10 +90,7 @@ extern NSMutableArray *RCTGetModuleClasses(void);
|
|||
}
|
||||
|
||||
NSDictionary *testCaseNames = [self waitForEvent:@"realm-test-names"];
|
||||
if (!testCaseNames.count) {
|
||||
NSLog(@"ERROR: No test names were provided by the JS");
|
||||
exit(1);
|
||||
}
|
||||
NSAssert(testCaseNames.count, @"No test names were provided by the JS");
|
||||
|
||||
NSString *nameSuffix = [self classNameSuffix];
|
||||
if (nameSuffix.length) {
|
||||
|
@ -115,25 +118,37 @@ extern NSMutableArray *RCTGetModuleClasses(void);
|
|||
notification = note;
|
||||
}];
|
||||
|
||||
[self waitForCondition:&condition];
|
||||
[nc removeObserver:token];
|
||||
@try {
|
||||
[self waitForCondition:&condition description:notificationName];
|
||||
} @finally {
|
||||
[nc removeObserver:token];
|
||||
}
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
+ (void)waitForCondition:(BOOL *)condition {
|
||||
+ (void)waitForCondition:(BOOL *)condition description:(NSString *)description {
|
||||
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
|
||||
NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:10.0];
|
||||
|
||||
while (!*condition) {
|
||||
if ([timeout timeIntervalSinceNow] < 0) {
|
||||
@throw [NSException exceptionWithName:@"ConditionTimeout"
|
||||
reason:[NSString stringWithFormat:@"Timed out waiting for: %@", description]
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
@autoreleasepool {
|
||||
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
|
||||
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
||||
[runLoop runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
||||
[NSThread sleepForTimeInterval:0.01]; // Bad things may happen without some sleep.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+ (id)waitForEvent:(NSString *)eventName {
|
||||
__weak RealmReact *realmModule = [[self currentBridge] moduleForClass:[RealmReact class]];
|
||||
assert(realmModule);
|
||||
NSAssert(realmModule, @"RealmReact module not found");
|
||||
|
||||
__block BOOL condition = NO;
|
||||
__block id result;
|
||||
|
@ -148,10 +163,28 @@ extern NSMutableArray *RCTGetModuleClasses(void);
|
|||
|
||||
[realmModule addListenerForEvent:eventName handler:handler];
|
||||
|
||||
[self waitForCondition:&condition];
|
||||
[self waitForCondition:&condition description:eventName];
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)invokeTest {
|
||||
RCTLogFunction logFunction = RCTGetLogFunction();
|
||||
|
||||
// Fail when React Native logs an error.
|
||||
RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
|
||||
RCTDefaultLogFunction(level, source, fileName, lineNumber, message);
|
||||
|
||||
if (level >= RCTLogLevelError) {
|
||||
NSString *type = (source == RCTLogSourceJavaScript) ? @"JS" : @"Native";
|
||||
XCTFail(@"%@ Error: %@", type, RCTFormatLog(nil, level, fileName, lineNumber, message));
|
||||
}
|
||||
});
|
||||
|
||||
[super invokeTest];
|
||||
|
||||
RCTSetLogFunction(logFunction);
|
||||
}
|
||||
|
||||
- (void)invokeMethod:(NSString *)method {
|
||||
NSString *module = NSStringFromClass(self.class);
|
||||
NSString *suffix = [self.class classNameSuffix];
|
||||
|
@ -163,7 +196,13 @@ extern NSMutableArray *RCTGetModuleClasses(void);
|
|||
RCTBridge *bridge = [self.class currentBridge];
|
||||
[bridge.eventDispatcher sendAppEventWithName:@"realm-run-test" body:@{@"suite": module, @"name": method}];
|
||||
|
||||
id error = [self.class waitForEvent:@"realm-test-finished"];
|
||||
id error;
|
||||
@try {
|
||||
error = [self.class waitForEvent:@"realm-test-finished"];
|
||||
} @catch (id exception) {
|
||||
error = exception;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
[self recordFailureWithDescription:[error description] inFile:@(__FILE__) atLine:__LINE__ expected:YES];
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue