-
-
Notifications
You must be signed in to change notification settings - Fork 237
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
594 additions
and
144 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
class ExceptionCause { | ||
ExceptionCause(this.exception, this.stackTrace); | ||
|
||
dynamic exception; | ||
dynamic stackTrace; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import 'exception_cause.dart'; | ||
import 'sentry_options.dart'; | ||
import 'throwable_mechanism.dart'; | ||
|
||
abstract class ExceptionCauseExtractor<T> { | ||
ExceptionCause? cause(T error); | ||
Type get exceptionType => T; | ||
} | ||
|
||
class RecursiveExceptionCauseExtractor { | ||
RecursiveExceptionCauseExtractor(this._options); | ||
|
||
final SentryOptions _options; | ||
|
||
List<ExceptionCause> flatten(exception, stackTrace) { | ||
final allExceptionCauses = <ExceptionCause>[]; | ||
final circularityDetector = <dynamic>{}; | ||
|
||
var currentException = exception; | ||
ExceptionCause? currentExceptionCause = | ||
ExceptionCause(exception, stackTrace); | ||
|
||
while (currentException != null && | ||
currentExceptionCause != null && | ||
circularityDetector.add(currentException)) { | ||
allExceptionCauses.add(currentExceptionCause); | ||
|
||
final extractionSourceSource = currentException is ThrowableMechanism | ||
? currentException.throwable | ||
: currentException; | ||
|
||
final extractor = | ||
_options.exceptionCauseExtractor(extractionSourceSource.runtimeType); | ||
|
||
currentExceptionCause = extractor?.cause(extractionSourceSource); | ||
currentException = currentExceptionCause?.exception; | ||
} | ||
return allExceptionCauses; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import 'package:sentry/src/exception_cause.dart'; | ||
import 'package:sentry/src/exception_cause_extractor.dart'; | ||
import 'package:sentry/src/protocol/mechanism.dart'; | ||
import 'package:sentry/src/sentry_options.dart'; | ||
import 'package:sentry/src/throwable_mechanism.dart'; | ||
import 'package:test/test.dart'; | ||
import 'mocks.dart'; | ||
|
||
void main() { | ||
late Fixture fixture; | ||
|
||
setUp(() { | ||
fixture = Fixture(); | ||
}); | ||
|
||
test('flatten', () { | ||
final errorC = ExceptionC(); | ||
final errorB = ExceptionB(errorC); | ||
final errorA = ExceptionA(errorB); | ||
|
||
fixture.options.addExceptionCauseExtractor( | ||
ExceptionACauseExtractor(), | ||
); | ||
|
||
fixture.options.addExceptionCauseExtractor( | ||
ExceptionBCauseExtractor(), | ||
); | ||
|
||
final sut = fixture.getSut(); | ||
|
||
final flattened = sut.flatten(errorA, null); | ||
final actual = flattened.map((exceptionCause) => exceptionCause.exception); | ||
expect(actual, [errorA, errorB, errorC]); | ||
}); | ||
|
||
test('flatten breaks circularity', () { | ||
final a = ExceptionCircularA(); | ||
final b = ExceptionCircularB(); | ||
a.other = b; | ||
b.other = a; | ||
|
||
fixture.options.addExceptionCauseExtractor( | ||
ExceptionCircularAExtractor(), | ||
); | ||
|
||
fixture.options.addExceptionCauseExtractor( | ||
ExceptionCircularBExtractor(), | ||
); | ||
|
||
final sut = fixture.getSut(); | ||
|
||
final flattened = sut.flatten(a, null); | ||
final actual = flattened.map((exceptionCause) => exceptionCause.exception); | ||
|
||
expect(actual, [a, b]); | ||
}); | ||
|
||
test('flatten preserves throwable mechanism', () { | ||
final errorC = ExceptionC(); | ||
final errorB = ExceptionB(errorC); | ||
final errorA = ExceptionA(errorB); | ||
|
||
fixture.options.addExceptionCauseExtractor( | ||
ExceptionACauseExtractor(), | ||
); | ||
|
||
fixture.options.addExceptionCauseExtractor( | ||
ExceptionBCauseExtractor(), | ||
); | ||
|
||
final mechanism = Mechanism(type: "foo"); | ||
final throwableMechanism = ThrowableMechanism(mechanism, errorA); | ||
|
||
final sut = fixture.getSut(); | ||
final flattened = sut.flatten(throwableMechanism, null); | ||
|
||
final actual = flattened.map((exceptionCause) => exceptionCause.exception); | ||
expect(actual, [throwableMechanism, errorB, errorC]); | ||
}); | ||
} | ||
|
||
class Fixture { | ||
final options = SentryOptions(dsn: fakeDsn); | ||
|
||
RecursiveExceptionCauseExtractor getSut() { | ||
return RecursiveExceptionCauseExtractor(options); | ||
} | ||
} | ||
|
||
class ExceptionA { | ||
ExceptionA(this.other); | ||
final ExceptionB? other; | ||
} | ||
|
||
class ExceptionB { | ||
ExceptionB(this.anotherOther); | ||
final ExceptionC? anotherOther; | ||
} | ||
|
||
class ExceptionC { | ||
// I am empty inside | ||
} | ||
|
||
class ExceptionACauseExtractor extends ExceptionCauseExtractor<ExceptionA> { | ||
@override | ||
ExceptionCause? cause(ExceptionA error) { | ||
return ExceptionCause(error.other, null); | ||
} | ||
} | ||
|
||
class ExceptionBCauseExtractor extends ExceptionCauseExtractor<ExceptionB> { | ||
@override | ||
ExceptionCause? cause(ExceptionB error) { | ||
return ExceptionCause(error.anotherOther, null); | ||
} | ||
} | ||
|
||
class ExceptionCircularA { | ||
ExceptionCircularB? other; | ||
} | ||
|
||
class ExceptionCircularB { | ||
ExceptionCircularA? other; | ||
} | ||
|
||
class ExceptionCircularAExtractor | ||
extends ExceptionCauseExtractor<ExceptionCircularA> { | ||
@override | ||
ExceptionCause? cause(ExceptionCircularA error) { | ||
return ExceptionCause(error.other, null); | ||
} | ||
} | ||
|
||
class ExceptionCircularBExtractor | ||
extends ExceptionCauseExtractor<ExceptionCircularB> { | ||
@override | ||
ExceptionCause? cause(ExceptionCircularB error) { | ||
return ExceptionCause(error.other, null); | ||
} | ||
} |
Oops, something went wrong.