Merge branch 'master' of github.com:dart-lang/coverage
diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml
index ab225ad..2606b39 100644
--- a/.github/workflows/test-package.yml
+++ b/.github/workflows/test-package.yml
@@ -23,9 +23,9 @@
         sdk: [dev]
     steps:
       - uses: actions/checkout@v2
-      - uses: dart-lang/setup-dart@v0.1
+      - uses: dart-lang/setup-dart@v1.0
         with:
-          channel: ${{ matrix.sdk }}
+          sdk: ${{ matrix.sdk }}
       - id: install
         name: Install dependencies
         run: dart pub get
@@ -47,27 +47,27 @@
       matrix:
         # Add macos-latest and/or windows-latest if relevant for this package.
         os: [ubuntu-latest]
-        sdk: [dev]
+        sdk: [2.12.0, dev]
     steps:
       - uses: actions/checkout@v2
-      - uses: dart-lang/setup-dart@v0.1
+      - uses: dart-lang/setup-dart@v1.0
         with:
-          channel: ${{ matrix.sdk }}
+          sdk: ${{ matrix.sdk }}
       - id: install
         name: Install dependencies
         run: dart pub get
       - name: Run VM tests
         run: dart test --platform vm
         if: always() && steps.install.outcome == 'success'
-  
+
   coverage:
     needs: test
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
-      - uses: dart-lang/setup-dart@v0.1
+      - uses: dart-lang/setup-dart@v1.0
         with:
-          channel: dev
+          sdk: dev
       - id: install
         name: Install dependencies
         run: dart pub get
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1256f4b..2da8fe8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,19 @@
   from using `Map<int, int>` to represent line coverage to using `HitMap`
   (which contains both line and function coverage).
 
+## 1.0.4 - 2021-06-08
+
+* Ensure `createHitmap` returns a sorted hitmap. This fixes a potential issue with
+  ignore line annotations.
+
+## 1.0.3 - 2021-05-25
+
+* Updated dependency on `vm_service` package from `^6.1.0` to `>=6.1.0 <8.0.0`.
+
+## 1.0.2 - 2021-03-15
+
+* Fix an issue where the `--packages` argument wasn't passed to `format_coverage`.
+
 ## 1.0.1 - 2021-02-25
 
 * Allow the chrome `sourceUriProvider` to return `null`.
@@ -19,6 +32,7 @@
 ## 1.0.0 - 2021-02-25
 
 * Migrate to null safety.
+* Removed support for SDK `1.x.x`.
 
 ## 0.15.2 - 2021-02-08
 
diff --git a/bin/format_coverage.dart b/bin/format_coverage.dart
index 49c6a65..0ca57fb 100644
--- a/bin/format_coverage.dart
+++ b/bin/format_coverage.dart
@@ -62,6 +62,7 @@
     files,
     env.workers,
     checkIgnoredLines: env.checkIgnore,
+    packagesPath: env.packagesPath,
   );
 
   // All workers are done. Process the data.
diff --git a/lib/src/hitmap.dart b/lib/src/hitmap.dart
index 2b048ed..40bd2c9 100644
--- a/lib/src/hitmap.dart
+++ b/lib/src/hitmap.dart
@@ -8,6 +8,7 @@
 import 'package:coverage/src/resolver.dart';
 import 'package:coverage/src/util.dart';
 
+
 /// Contains line and function hit information for a single script.
 class HitMap {
   /// Map from line to hit count for that line.
@@ -18,6 +19,20 @@
 
   /// Map from function definition line to function name.
   final funcNames = <int, String>{};
+
+/// Class containing information about a coverage hit.
+class _HitInfo {
+  _HitInfo(this.firstLine, this.hitRange, this.hitCount);
+
+  /// The line number of the first line of this hit range.
+  final int firstLine;
+
+  /// A hit range is either a number (1 line) or a String of the form
+  /// "start-end" (multi-line range).
+  final dynamic hitRange;
+
+  /// How many times this hit range was executed.
+  final int hitCount;
 }
 
 /// Creates a single hitmap from a raw json object. Throws away all entries that
@@ -86,6 +101,7 @@
       return false;
     }
 
+<<<<<<< HEAD
     void addToMap(Map<int, int> map, int line, int count) {
       final oldCount = map.putIfAbsent(line, () => 0);
       map[line] = count + oldCount;
@@ -115,6 +131,32 @@
           }
         } else {
           throw StateError('Expected value of type int or String');
+=======
+    final sourceHitMap = globalHitMap.putIfAbsent(source, () => <int, int>{});
+    var hits = e['hits'] as List;
+    // Ignore line annotations require hits to be sorted.
+    hits = _sortHits(hits);
+    // hits is a flat array of the following format:
+    // [ <line|linerange>, <hitcount>,...]
+    // line: number.
+    // linerange: '<line>-<line>'.
+    for (var i = 0; i < hits.length; i += 2) {
+      final k = hits[i];
+      if (k is int) {
+        // Single line.
+        if (_shouldIgnoreLine(ignoredLines, k)) continue;
+
+        addToMap(sourceHitMap, k, hits[i + 1] as int);
+      } else if (k is String) {
+        // Linerange. We expand line ranges to actual lines at this point.
+        final splitPos = k.indexOf('-');
+        final start = int.parse(k.substring(0, splitPos));
+        final end = int.parse(k.substring(splitPos + 1));
+        for (var j = start; j <= end; j++) {
+          if (_shouldIgnoreLine(ignoredLines, j)) continue;
+
+          addToMap(sourceHitMap, j, hits[i + 1] as int);
+>>>>>>> 4cf2e67b027b1e940b2019c463e7f9947f6a1b66
         }
       }
     }
@@ -167,6 +209,7 @@
   Iterable<File> files,
   int _, {
   bool checkIgnoredLines = false,
+  String? packagesPath,
 }) async {
   final globalHitmap = <String, HitMap>{};
   for (var file in files) {
@@ -178,6 +221,7 @@
         await createHitmap(
           jsonResult.cast<Map<String, dynamic>>(),
           checkIgnoredLines: checkIgnoredLines,
+          packagesPath: packagesPath,
         ),
         globalHitmap,
       );
@@ -186,6 +230,7 @@
   return globalHitmap;
 }
 
+<<<<<<< HEAD
 /// Returns a JSON hit map backward-compatible with pre-1.16.0 SDKs.
 Map<String, dynamic> toScriptCoverageJson(Uri scriptUri, HitMap hits) {
   final json = <String, dynamic>{};
@@ -211,4 +256,21 @@
   json['funcHits'] = flattenMap<int>(hits.funcHits);
   json['funcNames'] = flattenMap<dynamic>(hits.funcNames);
   return json;
+=======
+/// Sorts the hits array based on the line numbers.
+List _sortHits(List hits) {
+  final structuredHits = <_HitInfo>[];
+  for (var i = 0; i < hits.length - 1; i += 2) {
+    final lineOrLineRange = hits[i];
+    final firstLineInRange = lineOrLineRange is int
+        ? lineOrLineRange
+        : int.parse(lineOrLineRange.split('-')[0] as String);
+    structuredHits.add(_HitInfo(firstLineInRange, hits[i], hits[i + 1] as int));
+  }
+  structuredHits.sort((a, b) => a.firstLine.compareTo(b.firstLine));
+  return structuredHits
+      .map((item) => [item.hitRange, item.hitCount])
+      .expand((item) => item)
+      .toList();
+>>>>>>> 4cf2e67b027b1e940b2019c463e7f9947f6a1b66
 }
diff --git a/pubspec.yaml b/pubspec.yaml
index a118e91..f8898ab 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,10 +1,10 @@
 name: coverage
-version: 1.0.1
+version: 1.0.4
 description: Coverage data manipulation and formatting
 homepage: https://github.com/dart-lang/coverage
 
 environment:
-  sdk: '>=2.12.0-0 <3.0.0'
+  sdk: '>=2.12.0 <3.0.0'
 
 dependencies:
   args: ^2.0.0
@@ -13,7 +13,7 @@
   path: ^1.8.0
   source_maps: ^0.10.10
   stack_trace: ^1.10.0
-  vm_service: ^6.1.0
+  vm_service: ">=6.1.0 <8.0.0"
 dev_dependencies:
   pedantic: ^1.10.0
   test: ^1.16.0
@@ -21,7 +21,3 @@
 executables:
   collect_coverage:
   format_coverage:
-
-dependency_overrides:
-  test: ^1.16.0
-  test_core: ^0.3.14
diff --git a/test/collect_coverage_api_test.dart b/test/collect_coverage_api_test.dart
index 49d4efb..6bb6ce1 100644
--- a/test/collect_coverage_api_test.dart
+++ b/test/collect_coverage_api_test.dart
@@ -74,8 +74,8 @@
 
     final testAppCoverage = _getScriptCoverage(coverage, 'test_app.dart')!;
     var hits = testAppCoverage['hits'] as List<int>;
-    _expectHitCount(hits, 44, 0);
-    _expectHitCount(hits, 48, 0);
+    _expectHitCount(hits, 46, 0);
+    _expectHitCount(hits, 50, 0);
 
     final isolateCoverage =
         _getScriptCoverage(coverage, 'test_app_isolate.dart')!;
diff --git a/test/collect_coverage_test.dart b/test/collect_coverage_test.dart
index 6dd8ea1..b96f768 100644
--- a/test/collect_coverage_test.dart
+++ b/test/collect_coverage_test.dart
@@ -49,32 +49,75 @@
     }
   });
 
+  test('createHitmap returns a sorted hitmap', () async {
+    final coverage = [
+      {
+        'source': 'foo',
+        'script': '{type: @Script, fixedId: true, '
+            'id: bar.dart, uri: bar.dart, _kind: library}',
+        'hits': [
+          45,
+          1,
+          46,
+          1,
+          49,
+          0,
+          50,
+          0,
+          15,
+          1,
+          16,
+          2,
+          17,
+          2,
+        ]
+      }
+    ];
+    final hitMap = await createHitmap(
+      coverage.cast<Map<String, dynamic>>(),
+    );
+    final expectedHits = {15: 1, 16: 2, 17: 2, 45: 1, 46: 1, 49: 0, 50: 0};
+    expect(hitMap['foo'], expectedHits);
+  });
+
   test('createHitmap', () async {
     final resultString = await _getCoverageResult();
     final jsonResult = json.decode(resultString) as Map<String, dynamic>;
     final coverage = jsonResult['coverage'] as List;
     final hitMap = await createHitmap(
       coverage.cast<Map<String, dynamic>>(),
-      checkIgnoredLines: true,
     );
     expect(hitMap, contains(_sampleAppFileUri));
 
     final isolateFile = hitMap[_isolateLibFileUri];
-    final expectedLineHits = {
+    final expectedHits = {
+      11: 1,
       12: 1,
       13: 1,
       15: 0,
+      28: 1,
+      29: 1,
+      31: 1,
+      32: 3,
+      38: 1,
+      41: 1,
+      42: 1,
+      43: 1,
+      46: 1,
+      47: 1,
+      49: 1,
+      50: 1,
+      51: 1,
+      53: 1,
+      54: 1,
+      55: 1,
+      18: 1,
       19: 1,
       20: 1,
       22: 0,
-      29: 1,
-      31: 1,
-      32: 2,
       33: 1,
       34: 3,
-      35: 1,
-      46: 1,
-      47: 1,
+      35: 1
     };
     if (Platform.version.startsWith('1.')) {
       // Dart VMs prior to 2.0.0-dev.5.0 contain a bug that emits coverage on the
@@ -110,6 +153,26 @@
       await tempDir.delete(recursive: true);
     }
   });
+
+  test('parseCoverage with packagesPath and checkIgnoredLines', () async {
+    final tempDir = await Directory.systemTemp.createTemp('coverage.test.');
+
+    try {
+      final outputFile = File(p.join(tempDir.path, 'coverage.json'));
+
+      final coverageResults = await _getCoverageResult();
+      await outputFile.writeAsString(coverageResults, flush: true);
+
+      final parsedResult = await parseCoverage([outputFile], 1,
+          packagesPath: '.packages', checkIgnoredLines: true);
+
+      // This file has ignore:coverage-file.
+      expect(parsedResult, isNot(contains(_sampleAppFileUri)));
+      expect(parsedResult, contains(_isolateLibFileUri));
+    } finally {
+      await tempDir.delete(recursive: true);
+    }
+  });
 }
 
 String? _coverageData;
diff --git a/test/lcov_test.dart b/test/lcov_test.dart
index c9ace53..1788512 100644
--- a/test/lcov_test.dart
+++ b/test/lcov_test.dart
@@ -27,11 +27,11 @@
 
     final sampleAppHitMap = hitmap[_sampleAppFileUri];
 
-    expect(sampleAppHitMap, containsPair(44, greaterThanOrEqualTo(1)),
+    expect(sampleAppHitMap, containsPair(46, greaterThanOrEqualTo(1)),
         reason: 'be careful if you modify the test file');
-    expect(sampleAppHitMap, containsPair(48, 0),
+    expect(sampleAppHitMap, containsPair(50, 0),
         reason: 'be careful if you modify the test file');
-    expect(sampleAppHitMap, isNot(contains(30)),
+    expect(sampleAppHitMap, isNot(contains(32)),
         reason: 'be careful if you modify the test file');
   });
 
diff --git a/test/run_and_collect_test.dart b/test/run_and_collect_test.dart
index aa0fe2a..99b0e92 100644
--- a/test/run_and_collect_test.dart
+++ b/test/run_and_collect_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-import 'dart:io';
-
 import 'package:coverage/coverage.dart';
 import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
@@ -40,42 +38,30 @@
       expect(sampleCoverageData['hits'], isNotEmpty);
     }
 
-    final hitMap = await createHitmap(
-      coverage,
-      checkIgnoredLines: true,
-    );
-    expect(hitMap, contains(_sampleAppFileUri));
+    final hitMap = await createHitmap(coverage, checkIgnoredLines: true);
+    expect(hitMap, isNot(contains(_sampleAppFileUri)));
 
     final actualHits = hitMap[_isolateLibFileUri];
     final expectedHits = {
+      11: 1,
       12: 1,
       13: 1,
       15: 0,
+      28: 1,
+      29: 1,
+      31: 1,
+      32: 3,
+      46: 1,
+      47: 1,
+      18: 1,
       19: 1,
       20: 1,
       22: 0,
-      29: 1,
-      31: 1,
-      32: 2,
       33: 1,
       34: 3,
-      35: 1,
-      46: 1,
-      47: 1,
+      35: 1
     };
-    // Dart VMs prior to 2.0.0-dev.5.0 contain a bug that emits coverage on the
-    // closing brace of async function blocks.
-    // See: https://github.com/dart-lang/coverage/issues/196
-    if (Platform.version.startsWith('1.')) {
-      expectedHits[23] = 0;
-    } else {
-      // Dart VMs version 2.0.0-dev.6.0 mark the opening brace of a function as
-      // coverable.
-      expectedHits[11] = 1;
-      expectedHits[18] = 1;
-      expectedHits[28] = 1;
-      expectedHits[32] = 3;
-    }
+
     expect(actualHits, expectedHits);
   });
 }
diff --git a/test/test_files/test_app.dart b/test/test_files/test_app.dart
index 79c5f2a..bf9256f 100644
--- a/test/test_files/test_app.dart
+++ b/test/test_files/test_app.dart
@@ -2,6 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+// coverage:ignore-file
+
 import 'dart:async';
 import 'dart:developer';
 import 'dart:isolate';