feat(errors): preserve stack traces of user exceptions in Dart

This commit is contained in:
Yegor Jbanov 2015-05-15 11:46:34 -07:00
parent 421d8916a6
commit b6f29b4448
3 changed files with 146 additions and 2 deletions

View File

@ -6,7 +6,9 @@ export 'dart:async' show Future, Stream, StreamController, StreamSubscription;
class PromiseWrapper { class PromiseWrapper {
static Future resolve(obj) => new Future.value(obj); static Future resolve(obj) => new Future.value(obj);
static Future reject(obj) => new Future.error(obj); static Future reject(obj) => new Future.error(
obj,
obj is Error ? obj.stackTrace : null);
static Future<List> all(List<Future> promises) => Future.wait(promises); static Future<List> all(List<Future> promises) => Future.wait(promises);
@ -98,6 +100,10 @@ class _Completer {
} }
void reject(v) { void reject(v) {
c.completeError(v); var stack = null;
if (v is Error) {
stack = v.stackTrace;
}
c.completeError(v, stack);
} }
} }

View File

@ -6,6 +6,19 @@ import 'package:angular2/di.dart';
import 'package:angular2/src/test_lib/test_bed.dart'; import 'package:angular2/src/test_lib/test_bed.dart';
import 'package:angular2/test_lib.dart'; import 'package:angular2/test_lib.dart';
class MockException implements Error { var message; var stackTrace; }
void functionThatThrows() {
try { throw new MockException(); }
catch(e, stack) {
// If we lose the stack trace the message will no longer match
// the first line in the stack
e.message = stack.toString().split('\n')[0];
e.stackTrace = stack;
rethrow;
}
}
main() { main() {
describe('TypeLiteral', () { describe('TypeLiteral', () {
it('should publish via injectables', it('should publish via injectables',
@ -22,6 +35,21 @@ main() {
}); });
})); }));
}); });
describe('Error handling', () {
it('should preserve stack traces throws from components',
inject([TestBed, AsyncTestCompleter], (tb, async) {
tb.overrideView(Dummy, new View(
template: '<throwing-component></throwing-component>',
directives: [ThrowingComponent]
));
tb.createView(Dummy).catchError((e, stack) {
expect(stack.toString().split('\n')[0]).toEqual(e.message);
async.done();
});
}));
});
} }
@Component(selector: 'dummy') @Component(selector: 'dummy')
@ -43,3 +71,13 @@ class TypeLiteralComponent {
TypeLiteralComponent(this.list); TypeLiteralComponent(this.list);
} }
@Component(
selector: 'throwing-component'
)
@View(template: '')
class ThrowingComponent {
ThrowingComponent() {
functionThatThrows();
}
}

View File

@ -0,0 +1,100 @@
/// This file contains tests that make sense only in Dart
library angular2.test.facade.async_dart_spec;
import 'package:angular2/test_lib.dart';
import 'package:angular2/src/facade/async.dart';
class MockException implements Error { var message; var stackTrace; }
void functionThatThrows() {
try { throw new MockException(); }
catch(e, stack) {
// If we lose the stack trace the message will no longer match
// the first line in the stack
e.message = stack.toString().split('\n')[0];
e.stackTrace = stack;
rethrow;
}
}
void functionThatThrowsNonError() {
throw 'this is an error';
}
void expectFunctionThatThrowsWithStackTrace(
Future future, AsyncTestCompleter async) {
PromiseWrapper.catchError(future, (err, StackTrace stack) {
expect(stack.toString().split('\n')[0]).toEqual(err.message);
async.done();
});
}
void expectFunctionThatThrowsWithoutStackTrace(Future future,
AsyncTestCompleter async) {
PromiseWrapper.catchError(future, (err, StackTrace stack) {
expect(stack).toBe(null);
async.done();
});
}
main() {
describe('Completer', () {
it('should preserve error stack traces',
inject([AsyncTestCompleter], (async) {
var c = PromiseWrapper.completer();
expectFunctionThatThrowsWithStackTrace(c.promise, async);
try {
functionThatThrows();
} catch(e) {
c.reject(e);
}
}));
// TODO: We might fix this one day; for now testing it to be explicit
it('CANNOT preserve error stack traces for non-Errors',
inject([AsyncTestCompleter], (async) {
var c = PromiseWrapper.completer();
expectFunctionThatThrowsWithoutStackTrace(c.promise, async);
try {
functionThatThrowsNonError();
} catch(e) {
c.reject(e);
}
}));
});
describe('PromiseWrapper', () {
describe('reject', () {
it('should preserve error stack traces',
inject([AsyncTestCompleter], (async) {
try {
functionThatThrows();
} catch(e) {
var rejectedFuture = PromiseWrapper.reject(e);
expectFunctionThatThrowsWithStackTrace(rejectedFuture, async);
}
}));
// TODO: We might fix this one day; for now testing it to be explicit
it('CANNOT preserve stack traces for non-Errors',
inject([AsyncTestCompleter], (async) {
try {
functionThatThrowsNonError();
} catch(e) {
var rejectedFuture = PromiseWrapper.reject(e);
expectFunctionThatThrowsWithoutStackTrace(rejectedFuture, async);
}
}));
});
});
}