Merge pull request #29 from realm/test-failures
Test failures now report the real failure location
This commit is contained in:
commit
8e110081cd
|
@ -26,10 +26,6 @@ extern NSString *TestRealmPath();
|
||||||
|
|
||||||
+ (NSString *)jsSuiteName;
|
+ (NSString *)jsSuiteName;
|
||||||
|
|
||||||
@property JSGlobalContextRef ctx;
|
@property (nonatomic, readonly) JSContext *context;
|
||||||
@property (readonly) JSContext *context;
|
|
||||||
|
|
||||||
- (JSValueRef)performScript:(NSString *)script exception:(JSValueRef *)exception;
|
|
||||||
- (void)performTestScript:(NSString *)script;
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -55,9 +55,13 @@ static void DeleteRealmFilesAtPath(NSString *path) {
|
||||||
|
|
||||||
static JSClassRef s_globalClass;
|
static JSClassRef s_globalClass;
|
||||||
|
|
||||||
@implementation RealmJSTests {
|
@interface RealmJSTests ()
|
||||||
NSString *_jsTestSuite;
|
|
||||||
}
|
@property (nonatomic, strong) JSContext *context;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation RealmJSTests
|
||||||
|
|
||||||
+ (void)initialize {
|
+ (void)initialize {
|
||||||
JSClassDefinition globalDefinition = kJSClassDefinitionEmpty;
|
JSClassDefinition globalDefinition = kJSClassDefinitionEmpty;
|
||||||
|
@ -73,12 +77,18 @@ static JSClassRef s_globalClass;
|
||||||
[[NSFileManager defaultManager] createDirectoryAtPath:defaultDir withIntermediateDirectories:YES attributes:nil error:nil];
|
[[NSFileManager defaultManager] createDirectoryAtPath:defaultDir withIntermediateDirectories:YES attributes:nil error:nil];
|
||||||
RJSSetDefaultPath([defaultDir stringByAppendingPathComponent:@"default.realm"].UTF8String);
|
RJSSetDefaultPath([defaultDir stringByAppendingPathComponent:@"default.realm"].UTF8String);
|
||||||
|
|
||||||
_ctx = JSGlobalContextCreateInGroup(NULL, s_globalClass);
|
JSGlobalContextRef ctx = JSGlobalContextCreateInGroup(NULL, s_globalClass);
|
||||||
[RealmJS initializeContext:_ctx];
|
self.context = [JSContext contextWithJSGlobalContextRef:ctx];
|
||||||
|
|
||||||
|
[RealmJS initializeContext:ctx];
|
||||||
|
|
||||||
|
[self evaluateScriptWithName:@"TestCase"];
|
||||||
|
[self evaluateScriptWithName:@"TestObjects"];
|
||||||
|
[self evaluateScriptWithName:self.class.jsSuiteName];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)tearDown {
|
- (void)tearDown {
|
||||||
JSGlobalContextRelease(_ctx);
|
self.context = nil;
|
||||||
|
|
||||||
DeleteRealmFilesAtPath(TestRealmPath());
|
DeleteRealmFilesAtPath(TestRealmPath());
|
||||||
DeleteRealmFilesAtPath(@(RJSDefaultPath().c_str()));
|
DeleteRealmFilesAtPath(@(RJSDefaultPath().c_str()));
|
||||||
|
@ -92,25 +102,64 @@ static JSClassRef s_globalClass;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (JSContext *)context {
|
- (void)evaluateScriptWithName:(NSString *)name {
|
||||||
return [JSContext contextWithJSGlobalContextRef:_ctx];
|
NSURL *url = [self.class scriptURLWithName:name];
|
||||||
|
NSString *script = [self.class loadScriptWithURL:url];
|
||||||
|
|
||||||
|
[self evaluateScript:script fromURL:url];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (JSValueRef)performScript:(NSString *)script exception:(JSValueRef *)exception {
|
- (void)evaluateScript:(NSString *)script fromURL:(NSURL *)url {
|
||||||
*exception = NULL;
|
JSValue *exception;
|
||||||
JSStringRef jsScript = JSStringCreateWithUTF8CString(script.UTF8String);
|
[self.class evaluateScript:script fromURL:url inContext:self.context exception:&exception];
|
||||||
JSValueRef result = JSEvaluateScript(_ctx, jsScript, NULL, NULL, 0, exception);
|
|
||||||
JSStringRelease(jsScript);
|
if (exception) {
|
||||||
return result;
|
JSValue *message = [exception hasProperty:@"message"] ? exception[@"message"] : exception;
|
||||||
|
NSString *source = [exception hasProperty:@"sourceURL"] ? [exception[@"sourceURL"] toString] : nil;
|
||||||
|
NSUInteger line = [exception hasProperty:@"line"] ? [exception[@"line"] toUInt32] : 0;
|
||||||
|
NSURL *sourceURL = source ? [NSURL URLWithString:source.lastPathComponent relativeToURL:[NSURL URLWithString:@(__FILE__)]] : nil;
|
||||||
|
const char *sourcePath = sourceURL.absoluteString.UTF8String;
|
||||||
|
|
||||||
|
_XCTFailureHandler(self, YES, sourcePath ?: __FILE__, sourcePath ? line : __LINE__, @"JS", @"%@", message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)performTestScript:(NSString *)script {
|
+ (JSValue *)evaluateScript:(NSString *)script fromURL:(NSURL *)url inContext:(JSContext *)context exception:(JSValue **)exception {
|
||||||
JSValueRef e = NULL;
|
|
||||||
JSStringRef jsScript = JSStringCreateWithUTF8CString(script.UTF8String);
|
JSStringRef jsScript = JSStringCreateWithUTF8CString(script.UTF8String);
|
||||||
JSEvaluateScript(_ctx, jsScript, NULL, NULL, 0, &e);
|
JSStringRef jsURL = url ? JSStringCreateWithUTF8CString(url.absoluteString.UTF8String) : NULL;
|
||||||
JSStringRelease(jsScript);
|
JSValueRef jsException = NULL;
|
||||||
|
JSValueRef jsResult = JSEvaluateScript(context.JSGlobalContextRef, jsScript, NULL, jsURL, 1, &jsException);
|
||||||
|
|
||||||
XCTAssertFalse(e, @"%@", [JSValue valueWithJSValueRef:e inContext:self.context]);
|
JSStringRelease(jsScript);
|
||||||
|
if (jsURL) {
|
||||||
|
JSStringRelease(jsURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jsException) {
|
||||||
|
*exception = [JSValue valueWithJSValueRef:jsException inContext:context];
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [JSValue valueWithJSValueRef:jsResult inContext:context];
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSURL *)scriptURLWithName:(NSString *)name {
|
||||||
|
NSURL *url = [[NSBundle bundleForClass:self] URLForResource:name withExtension:@"js"];
|
||||||
|
if (!url) {
|
||||||
|
NSLog(@"JS file does not exist: %@", url);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSString *)loadScriptWithURL:(NSURL *)url {
|
||||||
|
NSError *error;
|
||||||
|
NSString *script = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
|
||||||
|
if (!script) {
|
||||||
|
NSLog(@"Error reading JS file (%@): %@", url, error);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
return script;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (NSString *)jsSuiteName {
|
+ (NSString *)jsSuiteName {
|
||||||
|
@ -118,76 +167,42 @@ static JSClassRef s_globalClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (NSString *)jsSuiteScript {
|
+ (NSString *)jsSuiteScript {
|
||||||
NSString *testFile = [self jsSuiteName];
|
NSString *name = [self jsSuiteName];
|
||||||
if (!testFile) {
|
return name ? [self loadScriptWithURL:[self scriptURLWithName:name]] : nil;
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
testFile = [[NSBundle bundleForClass:self] pathForResource:testFile ofType:@"js"];
|
|
||||||
assert(testFile);
|
|
||||||
|
|
||||||
NSString *script = [NSString stringWithContentsOfFile:testFile encoding:NSUTF8StringEncoding error:nil];
|
|
||||||
if (!script) {
|
|
||||||
NSLog(@"Test file '%@' does not exist", testFile);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
return script;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (NSString *)loadScript:(NSString *)name {
|
+ (XCTestSuite *)defaultTestSuite {
|
||||||
NSString *testFile = [[NSBundle bundleForClass:self] pathForResource:name ofType:@"js"];
|
|
||||||
NSString *script = [NSString stringWithContentsOfFile:testFile encoding:NSUTF8StringEncoding error:nil];
|
|
||||||
if (!script) {
|
|
||||||
NSLog(@"Test objects file '%@' does not exist", testFile);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
return script;
|
|
||||||
}
|
|
||||||
|
|
||||||
+(XCTestSuite *)defaultTestSuite {
|
|
||||||
XCTestSuite *suite = [super defaultTestSuite];
|
XCTestSuite *suite = [super defaultTestSuite];
|
||||||
|
NSString *suiteName = [self jsSuiteName];
|
||||||
NSString *script = [self jsSuiteScript];
|
NSURL *scriptURL = suiteName ? [self scriptURLWithName:suiteName] : nil;
|
||||||
|
NSString *script = scriptURL ? [self loadScriptWithURL:scriptURL] : nil;
|
||||||
if (!script) {
|
if (!script) {
|
||||||
return suite;
|
return suite;
|
||||||
}
|
}
|
||||||
|
|
||||||
JSGlobalContextRef context = JSGlobalContextCreate(NULL);
|
JSContext *context = [[JSContext alloc] init];
|
||||||
JSContext *jsContext = [JSContext contextWithJSGlobalContextRef:context];
|
JSValue *exception;
|
||||||
JSValueRef e = NULL;
|
|
||||||
|
|
||||||
JSStringRef jsScript = JSStringCreateWithUTF8CString(script.UTF8String);
|
[self evaluateScript:script fromURL:scriptURL inContext:context exception:&exception];
|
||||||
JSEvaluateScript(context, jsScript, NULL, NULL, 0, &e);
|
if (exception) {
|
||||||
JSStringRelease(jsScript);
|
NSLog(@"%@.js - %@", suiteName, exception);
|
||||||
if (e) {
|
|
||||||
NSLog(@"%@", [JSValue valueWithJSValueRef:e inContext:jsContext]);
|
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
script = [[self jsSuiteName] stringByAppendingString:@";"];
|
JSValue *suiteObject = [self evaluateScript:suiteName fromURL:nil inContext:context exception:&exception];
|
||||||
jsScript = JSStringCreateWithUTF8CString(script.UTF8String);
|
if (exception) {
|
||||||
JSValueRef suiteObjectValue = JSEvaluateScript(context, jsScript, NULL, NULL, 0, &e);
|
NSLog(@"%@.js - %@", suiteName, exception);
|
||||||
JSStringRelease(jsScript);
|
|
||||||
if (e) {
|
|
||||||
NSLog(@"%@", [JSValue valueWithJSValueRef:e inContext:jsContext]);
|
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
JSObjectRef suiteObject = JSValueToObject(context, suiteObjectValue, &e);
|
if (![suiteObject isObject]) {
|
||||||
if (e) {
|
NSLog(@"%@.js - JS test suite is not an object: %@", suiteName, suiteObject);
|
||||||
NSLog(@"%@", [JSValue valueWithJSValueRef:e inContext:jsContext]);
|
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
JSPropertyNameArrayRef testNames = JSObjectCopyPropertyNames(context, suiteObject);
|
for (NSString *testName in [suiteObject toDictionary]) {
|
||||||
size_t count = JSPropertyNameArrayGetCount(testNames);
|
[suite addTest:[self testCaseWithSelector:NSSelectorFromString(testName)]];
|
||||||
for (size_t i = 0; i < count; i++) {
|
|
||||||
JSStringRef jsName = JSPropertyNameArrayGetNameAtIndex(testNames, i);
|
|
||||||
[suite addTest:[self testCaseWithSelector:NSSelectorFromString(@(RJSStringForJSString(jsName).c_str()))]];
|
|
||||||
}
|
}
|
||||||
JSPropertyNameArrayRelease(testNames);
|
|
||||||
|
|
||||||
JSGlobalContextRelease(context);
|
|
||||||
|
|
||||||
return suite;
|
return suite;
|
||||||
}
|
}
|
||||||
|
@ -201,12 +216,8 @@ static JSClassRef s_globalClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)forwardInvocation:(NSInvocation *)anInvocation {
|
- (void)forwardInvocation:(NSInvocation *)anInvocation {
|
||||||
[self performTestScript:[NSString stringWithFormat:@"%@;%@;%@;\n%@.%@();",
|
NSString *script = [NSString stringWithFormat:@"%@.%@();", [self.class jsSuiteName], NSStringFromSelector(anInvocation.selector)];
|
||||||
[self.class loadScript:@"TestCase"],
|
[self evaluateScript:script fromURL:nil];
|
||||||
[self.class loadScript:@"TestObjects"],
|
|
||||||
self.class.jsSuiteScript,
|
|
||||||
self.class.jsSuiteName,
|
|
||||||
NSStringFromSelector(anInvocation.selector)]];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -26,33 +26,33 @@ var TestUtil = {
|
||||||
};
|
};
|
||||||
|
|
||||||
var TestCase = {
|
var TestCase = {
|
||||||
assertEqual: function() {
|
assertEqual: function(val1, val2, errorMessage) {
|
||||||
if (arguments[0] !== arguments[1]) {
|
if (val1 !== val2) {
|
||||||
var message = "'" + arguments[0] + "' does not equal expected value '" + arguments[1] + "'";
|
var message = "'" + val1 + "' does not equal expected value '" + val2 + "'";
|
||||||
if (arguments.length == 3) {
|
if (errorMessage) {
|
||||||
message = arguments[2] + "\n" + message;
|
message = errorMessage + "\n" + message;
|
||||||
}
|
}
|
||||||
throw new Error(message);
|
throw new TestFailureError(message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
assertNotEqual: function() {
|
assertNotEqual: function(val1, val2, errorMessage) {
|
||||||
if (arguments[0] === arguments[1]) {
|
if (val1 === val2) {
|
||||||
var message = "'" + arguments[0] + "' equals '" + arguments[1] + "'";
|
var message = "'" + val1 + "' equals '" + val2 + "'";
|
||||||
if (arguments.length == 3) {
|
if (errorMessage) {
|
||||||
message = arguments[2] + "\n" + message;
|
message = errorMessage + "\n" + message;
|
||||||
}
|
}
|
||||||
throw new Error(message);
|
throw new TestFailureError(message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
assertEqualWithTolerance: function(val1, val2, tolerance, errorMessage) {
|
assertEqualWithTolerance: function(val1, val2, tolerance, errorMessage) {
|
||||||
if (val1 < val2 - tolerance || val1 > val2 + tolerance) {
|
if (val1 < val2 - tolerance || val1 > val2 + tolerance) {
|
||||||
var message = "'" + val1 + "' does not equal '" + val2 + "' with tolerance '" + tolerance + "'";
|
var message = "'" + val1 + "' does not equal '" + val2 + "' with tolerance '" + tolerance + "'";
|
||||||
if (errorMessage !== undefined) {
|
if (errorMessage) {
|
||||||
message = errorMessage + "\n" + message;
|
message = errorMessage + "\n" + message;
|
||||||
}
|
}
|
||||||
throw new Error(message);
|
throw new TestFailureError(message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -61,25 +61,43 @@ var TestCase = {
|
||||||
try {
|
try {
|
||||||
func();
|
func();
|
||||||
}
|
}
|
||||||
catch(exception) {
|
catch (e) {
|
||||||
caught = true;
|
caught = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!caught) {
|
if (!caught) {
|
||||||
if (errorMessage == undefined) {
|
throw new TestFailureError(errorMessage || 'Expected exception not thrown');
|
||||||
errorMessage = 'Expected exception not thrown: ';
|
|
||||||
}
|
|
||||||
throw errorMessage;
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
assertTrue: function(condition, errorMessage) {
|
assertTrue: function(condition, errorMessage) {
|
||||||
if (!condition) {
|
if (!condition) {
|
||||||
|
throw new TestFailureError(errorMessage || 'Condition expected to be true');
|
||||||
if (errorMessage == undefined) {
|
|
||||||
errorMessage = 'Condition expected to be true';
|
|
||||||
}
|
|
||||||
throw errorMessage;
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function TestFailureError(message) {
|
||||||
|
var error;
|
||||||
|
try {
|
||||||
|
throw new Error(message);
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This regular expression will match stack trace lines provided by JavaScriptCore.
|
||||||
|
// Example: someMethod@file:///path/to/file.js:10:24
|
||||||
|
var regex = /^(?:.*?@)?([^\[\(].+?):(\d+)(?::(\d+))?/;
|
||||||
|
|
||||||
|
// Remove the top two stack frames and use information from the third, if possible.
|
||||||
|
var stack = error.stack && error.stack.split('\n');
|
||||||
|
var match = stack[2] && stack[2].match(regex);
|
||||||
|
if (match) {
|
||||||
|
this.sourceURL = match[1];
|
||||||
|
this.line = +match[2];
|
||||||
|
this.column = +match[3];
|
||||||
|
this.stack = stack.slice(2).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.__proto__ = error;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue