- Add VariableSpec class with magnitude field validation - Add Formula class supporting multiple input/output variables - Support d4rt_code as string or object with code field - Add comprehensive tests for parsing and serialization - Fix broken test import in pruebas_d4rt_test.dart Follows README.md format requirements exactly
171 lines
5.4 KiB
Dart
171 lines
5.4 KiB
Dart
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
|
|
// 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 'package:source_maps/parser.dart';
|
|
|
|
import 'hitmap.dart';
|
|
|
|
/// Returns a Dart based hit-map containing coverage report for the provided
|
|
/// Chrome [preciseCoverage].
|
|
///
|
|
/// [sourceProvider] returns the source content for the Chrome scriptId, or null
|
|
/// if not available.
|
|
///
|
|
/// [sourceMapProvider] returns the associated source map content for the Chrome
|
|
/// scriptId, or null if not available.
|
|
///
|
|
/// [sourceUriProvider] returns the uri for the provided sourceUrl and
|
|
/// associated scriptId, or null if not available.
|
|
///
|
|
/// Chrome coverage information for which the corresponding source map or source
|
|
/// content is null will be ignored.
|
|
Future<Map<String, dynamic>> parseChromeCoverage(
|
|
List<Map<String, dynamic>> preciseCoverage,
|
|
Future<String?> Function(String scriptId) sourceProvider,
|
|
Future<String?> Function(String scriptId) sourceMapProvider,
|
|
Future<Uri?> Function(String sourceUrl, String scriptId) sourceUriProvider,
|
|
) async {
|
|
final coverageReport = <Uri, Map<int, bool>>{};
|
|
for (var entry in preciseCoverage) {
|
|
final scriptId = entry['scriptId'] as String;
|
|
|
|
final mapResponse = await sourceMapProvider(scriptId);
|
|
if (mapResponse == null) continue;
|
|
|
|
SingleMapping mapping;
|
|
try {
|
|
mapping = parse(mapResponse) as SingleMapping;
|
|
} on FormatException {
|
|
continue;
|
|
// ignore: avoid_catching_errors
|
|
} on ArgumentError {
|
|
continue;
|
|
}
|
|
|
|
final compiledSource = await sourceProvider(scriptId);
|
|
if (compiledSource == null) continue;
|
|
|
|
final coverageInfo = _coverageInfoFor(entry);
|
|
final offsetCoverage = _offsetCoverage(coverageInfo, compiledSource.length);
|
|
final coveredPositions = _coveredPositions(compiledSource, offsetCoverage);
|
|
|
|
for (var lineEntry in mapping.lines) {
|
|
for (var columnEntry in lineEntry.entries) {
|
|
final sourceUrlId = columnEntry.sourceUrlId;
|
|
if (sourceUrlId == null) continue;
|
|
final sourceUrl = mapping.urls[sourceUrlId];
|
|
|
|
// Ignore coverage information for the SDK.
|
|
if (sourceUrl.startsWith('org-dartlang-sdk:')) continue;
|
|
|
|
final uri = await sourceUriProvider(sourceUrl, scriptId);
|
|
if (uri == null) continue;
|
|
final coverage = coverageReport.putIfAbsent(uri, () => <int, bool>{});
|
|
|
|
final sourceLine = columnEntry.sourceLine!;
|
|
final current = coverage[sourceLine + 1] ?? false;
|
|
coverage[sourceLine + 1] = current ||
|
|
coveredPositions.contains(
|
|
_Position(lineEntry.line + 1, columnEntry.column + 1));
|
|
}
|
|
}
|
|
}
|
|
|
|
final coverageHitMaps = <Uri, HitMap>{};
|
|
coverageReport.forEach((uri, coverage) {
|
|
final hitMap = HitMap();
|
|
for (var line in coverage.keys.toList()..sort()) {
|
|
hitMap.lineHits[line] = coverage[line]! ? 1 : 0;
|
|
}
|
|
coverageHitMaps[uri] = hitMap;
|
|
});
|
|
|
|
final allCoverage = <Map<String, dynamic>>[];
|
|
coverageHitMaps.forEach((uri, hitMap) {
|
|
allCoverage.add(hitmapToJson(hitMap, uri));
|
|
});
|
|
return <String, dynamic>{'type': 'CodeCoverage', 'coverage': allCoverage};
|
|
}
|
|
|
|
/// Returns all covered positions in a provided source.
|
|
Set<_Position> _coveredPositions(
|
|
String compiledSource, List<bool> offsetCoverage) {
|
|
final positions = <_Position>{};
|
|
// Line is 1 based.
|
|
var line = 1;
|
|
// Column is 1 based.
|
|
var column = 0;
|
|
for (var offset = 0; offset < compiledSource.length; offset++) {
|
|
if (compiledSource[offset] == '\n') {
|
|
line++;
|
|
column = 0;
|
|
} else {
|
|
column++;
|
|
}
|
|
if (offsetCoverage[offset]) positions.add(_Position(line, column));
|
|
}
|
|
return positions;
|
|
}
|
|
|
|
/// Returns coverage information for a Chrome entry.
|
|
List<_CoverageInfo> _coverageInfoFor(Map<String, dynamic> entry) {
|
|
final result = <_CoverageInfo>[];
|
|
for (var functions
|
|
in (entry['functions'] as List).cast<Map<String, dynamic>>()) {
|
|
for (var range
|
|
in (functions['ranges'] as List).cast<Map<String, dynamic>>()) {
|
|
result.add(_CoverageInfo(
|
|
range['startOffset'] as int,
|
|
range['endOffset'] as int,
|
|
(range['count'] as int) > 0,
|
|
));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// Returns the coverage information for each offset.
|
|
List<bool> _offsetCoverage(List<_CoverageInfo> coverageInfo, int sourceLength) {
|
|
final offsetCoverage = List.filled(sourceLength, false);
|
|
|
|
// Sort coverage information by their size.
|
|
// Coverage information takes granularity as precedence.
|
|
coverageInfo.sort((a, b) =>
|
|
(b.endOffset - b.startOffset).compareTo(a.endOffset - a.startOffset));
|
|
|
|
for (var range in coverageInfo) {
|
|
for (var i = range.startOffset; i < range.endOffset; i++) {
|
|
offsetCoverage[i] = range.isCovered;
|
|
}
|
|
}
|
|
|
|
return offsetCoverage;
|
|
}
|
|
|
|
class _CoverageInfo {
|
|
_CoverageInfo(this.startOffset, this.endOffset, this.isCovered);
|
|
|
|
/// 0 based byte offset.
|
|
final int startOffset;
|
|
|
|
/// 0 based byte offset.
|
|
final int endOffset;
|
|
|
|
final bool isCovered;
|
|
}
|
|
|
|
/// A covered position in a source file where [line] and [column] are 1 based.
|
|
class _Position {
|
|
_Position(this.line, this.column);
|
|
|
|
final int line;
|
|
final int column;
|
|
|
|
@override
|
|
int get hashCode => Object.hash(line, column);
|
|
|
|
@override
|
|
bool operator ==(Object o) =>
|
|
o is _Position && o.line == line && o.column == column;
|
|
}
|