Fix async gap handling. (#84)

Fix an issue where an async gap at the end of a stack trace would not
get parsed correctly due to the trailing newline being `trim()`'d.

Add tests to cover this case.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c95d405..a738145 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
 ## 1.10.0-nullsafety.3-dev
 
+* Fix bug parsing asynchronous suspension gap markers at the end of stack
+  traces.
+
 ## 1.10.0-nullsafety.2
 
 * Forward fix for a change in SDK type promotion behavior.
diff --git a/lib/src/trace.dart b/lib/src/trace.dart
index dc7fd5d..7e30b95 100644
--- a/lib/src/trace.dart
+++ b/lib/src/trace.dart
@@ -146,7 +146,11 @@
   static List<Frame> _parseVM(String trace) {
     // Ignore [vmChainGap]. This matches the behavior of
     // `Chain.parse().toTrace()`.
-    var lines = trace.trim().replaceAll(vmChainGap, '').split('\n');
+    var lines = trace
+        .trim()
+        .replaceAll(vmChainGap, '')
+        .split('\n')
+        .where((line) => line.isNotEmpty);
     var frames = lines
         .take(lines.length - 1)
         .map((line) => Frame.parseVM(line))
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 838a093..0dd1755 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -8,7 +8,7 @@
 
 /// The line used in the string representation of VM stack chains to represent
 /// the gap between traces.
-const vmChainGap = '<asynchronous suspension>\n';
+final vmChainGap = RegExp(r'^<asynchronous suspension>\n?$', multiLine: true);
 
 // TODO(nweiz): When cross-platform imports work, use them to set this.
 /// Whether we're running in a JS context.
diff --git a/test/trace_test.dart b/test/trace_test.dart
index a10c69d..ea48e03 100644
--- a/test/trace_test.dart
+++ b/test/trace_test.dart
@@ -249,6 +249,25 @@
           equals(Uri.parse('https://dart.dev/foo/quux.dart')));
     });
 
+    test('parses a package:stack_trace stack chain with end gap correctly', () {
+      var trace =
+          Trace.parse('https://dart.dev/foo/bar.dart 10:11  Foo.<fn>.bar\n'
+              'https://dart.dev/foo/baz.dart        Foo.<fn>.bar\n'
+              'https://dart.dev/foo/bang.dart 10:11  Foo.<fn>.bar\n'
+              'https://dart.dev/foo/quux.dart        Foo.<fn>.bar'
+              '===== asynchronous gap ===========================\n');
+
+      expect(trace.frames.length, 4);
+      expect(trace.frames[0].uri,
+          equals(Uri.parse('https://dart.dev/foo/bar.dart')));
+      expect(trace.frames[1].uri,
+          equals(Uri.parse('https://dart.dev/foo/baz.dart')));
+      expect(trace.frames[2].uri,
+          equals(Uri.parse('https://dart.dev/foo/bang.dart')));
+      expect(trace.frames[3].uri,
+          equals(Uri.parse('https://dart.dev/foo/quux.dart')));
+    });
+
     test('parses a real package:stack_trace stack trace correctly', () {
       var traceString = Trace.current().toString();
       expect(Trace.parse(traceString).toString(), equals(traceString));
@@ -259,6 +278,28 @@
       expect(trace.frames, isEmpty);
       expect(trace.toString(), equals(''));
     });
+
+    test('parses trace with async gap correctly', () {
+      var trace = Trace.parse('#0      bop (file:///pull.dart:42:23)\n'
+          '<asynchronous suspension>\n'
+          '#1      twist (dart:the/future.dart:0:2)\n'
+          '#2      main (dart:my/file.dart:4:6)\n');
+
+      expect(trace.frames.length, 3);
+      expect(trace.frames[0].uri, equals(Uri.parse('file:///pull.dart')));
+      expect(trace.frames[1].uri, equals(Uri.parse('dart:the/future.dart')));
+      expect(trace.frames[2].uri, equals(Uri.parse('dart:my/file.dart')));
+    });
+
+    test('parses trace with async gap at end correctly', () {
+      var trace = Trace.parse('#0      bop (file:///pull.dart:42:23)\n'
+          '#1      twist (dart:the/future.dart:0:2)\n'
+          '<asynchronous suspension>\n');
+
+      expect(trace.frames.length, 2);
+      expect(trace.frames[0].uri, equals(Uri.parse('file:///pull.dart')));
+      expect(trace.frames[1].uri, equals(Uri.parse('dart:the/future.dart')));
+    });
   });
 
   test('.toString() nicely formats the stack trace', () {