WIP: Handle SentinelException (#424)

* Handle SentinelException

* Fix analysis

* Update changelog

* Remove benchmark from CI
diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml
index 4e2de79..c8cb613 100644
--- a/.github/workflows/test-package.yml
+++ b/.github/workflows/test-package.yml
@@ -77,31 +77,3 @@
         with:
           github-token: ${{ secrets.GITHUB_TOKEN }}
           path-to-lcov: coverage/lcov.info
-
-  benchmark:
-    needs: test
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-      - uses: dart-lang/setup-dart@v1.0
-        with:
-          sdk: dev
-      - name: Install dependencies
-        run: dart pub get
-      - name: Run benchmark
-        run: dart run benchmark/run_benchmarks.dart
-      - name: Download previous benchmark data
-        uses: actions/cache@v1
-        with:
-          path: benchmark/data/cache
-          key: ${{ runner.os }}-benchmark
-      - name: Check benchmark result
-        uses: benchmark-action/github-action-benchmark@v1
-        with:
-          tool: 'customSmallerIsBetter'
-          github-token: ${{ secrets.GITHUB_TOKEN }}
-          output-file-path: benchmark/data/benchmark_result.json
-          external-data-json-path: benchmark/data/cache/benchmark_result.json
-          fail-on-alert: true
-          comment-always: true
-          alert-threshold: 150%
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7690826..a8545f3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 1.6.1
+
+- Handle SentinelExceptions thrown by vm_service.
+
 ## 1.6.0
 
 - Update to vm_service 9.4.0.
diff --git a/lib/src/collect.dart b/lib/src/collect.dart
index f16f3f6..d33f9e9 100644
--- a/lib/src/collect.dart
+++ b/lib/src/collect.dart
@@ -158,32 +158,47 @@
       coveredIsolateGroups.add(isolateGroupId);
     }
     if (scopedOutput.isNotEmpty && !libraryFilters) {
-      final scripts = await service.getScripts(isolateRef.id!);
-      for (var script in scripts.scripts!) {
+      late final ScriptList scripts;
+      try {
+        scripts = await service.getScripts(isolateRef.id!);
+      } on SentinelException {
+        continue;
+      }
+      for (final script in scripts.scripts!) {
         final uri = Uri.parse(script.uri!);
         if (uri.scheme != 'package') continue;
         final scope = uri.path.split('/').first;
         // Skip scripts which should not be included in the report.
         if (!scopedOutput.contains(scope)) continue;
-        final scriptReport = await service.getSourceReport(
-            isolateRef.id!, sourceReportKinds,
-            forceCompile: true,
-            scriptId: script.id,
-            reportLines: reportLines ? true : null);
+        late final SourceReport scriptReport;
+        try {
+          scriptReport = await service.getSourceReport(
+              isolateRef.id!, sourceReportKinds,
+              forceCompile: true,
+              scriptId: script.id,
+              reportLines: reportLines ? true : null);
+        } on SentinelException {
+          continue;
+        }
         final coverage = await _getCoverageJson(service, isolateRef,
             scriptReport, includeDart, functionCoverage, reportLines);
         allCoverage.addAll(coverage);
       }
     } else {
-      final isolateReport = await service.getSourceReport(
-        isolateRef.id!,
-        sourceReportKinds,
-        forceCompile: true,
-        reportLines: reportLines ? true : null,
-        libraryFilters: scopedOutput.isNotEmpty && libraryFilters
-            ? List.from(scopedOutput.map((filter) => 'package:$filter/'))
-            : null,
-      );
+      late final SourceReport isolateReport;
+      try {
+        isolateReport = await service.getSourceReport(
+          isolateRef.id!,
+          sourceReportKinds,
+          forceCompile: true,
+          reportLines: reportLines ? true : null,
+          libraryFilters: scopedOutput.isNotEmpty && libraryFilters
+              ? List.from(scopedOutput.map((filter) => 'package:$filter/'))
+              : null,
+        );
+      } on SentinelException {
+        continue;
+      }
       final coverage = await _getCoverageJson(service, isolateRef,
           isolateReport, includeDart, functionCoverage, reportLines);
       allCoverage.addAll(coverage);
diff --git a/pubspec.yaml b/pubspec.yaml
index 760e64b..af786d6 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: coverage
-version: 1.6.0
+version: 1.6.1
 description: Coverage data manipulation and formatting
 repository: https://github.com/dart-lang/coverage
 
diff --git a/test/collect_coverage_mock_test.dart b/test/collect_coverage_mock_test.dart
index 3e02e9e..7ae6152 100644
--- a/test/collect_coverage_mock_test.dart
+++ b/test/collect_coverage_mock_test.dart
@@ -25,6 +25,11 @@
 IsolateGroup _isoGroup(String id, List<IsolateRef> isolates) =>
     IsolateGroup(id: id, isolates: isolates);
 
+class FakeSentinelException implements SentinelException {
+  @override
+  dynamic noSuchMethod(Invocation invocation) {}
+}
+
 MockVmService _mockService(
   int majorVersion,
   int minorVersion, {
@@ -372,5 +377,83 @@
       verifyNever(service.getIsolateGroup('isolateGroupA'));
       verifyNever(service.getIsolateGroup('isolateGroupB'));
     });
+
+    test(
+        'Collect coverage, scoped output, no library filters, '
+        'handles SentinelException from getScripts', () async {
+      final service = _mockService(3, 0);
+      when(service.getScripts('isolate')).thenThrow(FakeSentinelException());
+
+      final jsonResult = await collect(
+          Uri(), false, false, false, {'foo', 'bar'},
+          serviceOverrideForTesting: service);
+      final result = await HitMap.parseJson(
+          jsonResult['coverage'] as List<Map<String, dynamic>>);
+
+      expect(result.length, 0);
+    });
+
+    test(
+        'Collect coverage, scoped output, no library filters, '
+        'handles SentinelException from getSourceReport', () async {
+      final service = _mockService(3, 51);
+      when(service.getScripts('isolate')).thenAnswer((_) async => ScriptList(
+            scripts: [
+              ScriptRef(
+                uri: 'package:foo/foo.dart',
+                id: 'foo',
+              ),
+              ScriptRef(
+                uri: 'package:bar/bar.dart',
+                id: 'bar',
+              ),
+            ],
+          ));
+      when(service.getSourceReport('isolate', ['Coverage'],
+              scriptId: 'foo', forceCompile: true, reportLines: true))
+          .thenThrow(FakeSentinelException());
+      when(service.getSourceReport('isolate', ['Coverage'],
+              scriptId: 'bar', forceCompile: true, reportLines: true))
+          .thenAnswer((_) async => SourceReport(
+                ranges: [
+                  _range(
+                    0,
+                    SourceReportCoverage(
+                      hits: [95],
+                      misses: [52],
+                    ),
+                  ),
+                ],
+                scripts: [
+                  ScriptRef(
+                    uri: 'package:bar/bar.dart',
+                    id: 'bar',
+                  ),
+                ],
+              ));
+
+      final jsonResult = await collect(
+          Uri(), false, false, false, {'foo', 'bar'},
+          serviceOverrideForTesting: service);
+      final result = await HitMap.parseJson(
+          jsonResult['coverage'] as List<Map<String, dynamic>>);
+      expect(result.length, 1);
+      expect(result['package:bar/bar.dart']?.lineHits, {95: 1, 52: 0});
+    });
+
+    test(
+        'Collect coverage, no scoped output, '
+        'handles SentinelException from getSourceReport', () async {
+      final service = _mockService(3, 0);
+      when(service.getSourceReport('isolate', ['Coverage'], forceCompile: true))
+          .thenThrow(FakeSentinelException());
+
+      final jsonResult = await collect(Uri(), false, false, false, null,
+          serviceOverrideForTesting: service);
+      final result = await HitMap.parseJson(
+          jsonResult['coverage'] as List<Map<String, dynamic>>);
+
+      expect(result.length, 0);
+    });
   });
 }