From a75eb7ef85e4a232311cbaa5e52a15d282d92c9a Mon Sep 17 00:00:00 2001 From: Yash Date: Thu, 6 Mar 2025 04:12:26 +0530 Subject: [PATCH 1/3] Added uncovered files detection issue #529 --- pkgs/coverage/analysis_options.yaml | 2 + pkgs/coverage/lib/src/formatter.dart | 66 +++++++++++++++----- pkgs/coverage/lib/src/resolver.dart | 13 ++++ pkgs/coverage/test/run_and_collect_test.dart | 4 ++ 4 files changed, 68 insertions(+), 17 deletions(-) diff --git a/pkgs/coverage/analysis_options.yaml b/pkgs/coverage/analysis_options.yaml index bb1afe05ab..62aab638e0 100644 --- a/pkgs/coverage/analysis_options.yaml +++ b/pkgs/coverage/analysis_options.yaml @@ -1,6 +1,8 @@ include: package:dart_flutter_team_lints/analysis_options.yaml analyzer: + errors: + unnecessary_this: ignore language: strict-casts: true diff --git a/pkgs/coverage/lib/src/formatter.dart b/pkgs/coverage/lib/src/formatter.dart index a37df73b7f..1d305c4bea 100644 --- a/pkgs/coverage/lib/src/formatter.dart +++ b/pkgs/coverage/lib/src/formatter.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. +// ignore_for_file: unnecessary_this + import 'package:glob/glob.dart'; import 'package:path/path.dart' as p; @@ -78,12 +80,19 @@ extension FileHitMapsFormatter on Map { String? basePath, List? reportOn, Set? ignoreGlobs, + bool Function(String path)? includeUncovered, }) { final pathFilter = _getPathFilter( reportOn: reportOn, ignoreGlobs: ignoreGlobs, ); final buf = StringBuffer(); + + // Get all Dart files in the project + final allDartFiles = resolver.listAllDartFiles().toSet(); + final coveredFiles = this.keys.toSet(); + final uncoveredFiles = allDartFiles.difference(coveredFiles); + for (final entry in entries) { final v = entry.value; final lineHits = v.lineHits; @@ -91,13 +100,7 @@ extension FileHitMapsFormatter on Map { final funcNames = v.funcNames; final branchHits = v.branchHits; var source = resolver.resolve(entry.key); - if (source == null) { - continue; - } - - if (!pathFilter(source)) { - continue; - } + if (source == null || !pathFilter(source)) continue; if (basePath != null) { source = p.relative(source, from: basePath); @@ -129,6 +132,21 @@ extension FileHitMapsFormatter on Map { buf.write('end_of_record\n'); } + // Add uncovered files if allowed + for (final file in uncoveredFiles) { + if (includeUncovered != null && !includeUncovered(file)) continue; + var source = resolver.resolve(file); + if (source == null || !pathFilter(source)) continue; + if (basePath != null) { + source = p.relative(source, from: basePath); + } + + buf.write('SF:$source\n'); + buf.write('LF:0\n'); + buf.write('LH:0\n'); + buf.write('end_of_record\n'); + } + return buf.toString(); } @@ -144,12 +162,19 @@ extension FileHitMapsFormatter on Map { Set? ignoreGlobs, bool reportFuncs = false, bool reportBranches = false, + bool Function(String path)? includeUncovered, }) async { final pathFilter = _getPathFilter( reportOn: reportOn, ignoreGlobs: ignoreGlobs, ); final buf = StringBuffer(); + + // Get all Dart files in the project + final allDartFiles = resolver.listAllDartFiles().toSet(); + final coveredFiles = this.keys.toSet(); + final uncoveredFiles = allDartFiles.difference(coveredFiles); + for (final entry in entries) { final v = entry.value; if (reportFuncs && v.funcHits == null) { @@ -171,18 +196,10 @@ extension FileHitMapsFormatter on Map { ? v.branchHits! : v.lineHits; final source = resolver.resolve(entry.key); - if (source == null) { - continue; - } - - if (!pathFilter(source)) { - continue; - } + if (source == null || !pathFilter(source)) continue; final lines = await loader.load(source); - if (lines == null) { - continue; - } + if (lines == null) continue; buf.writeln(source); for (var line = 1; line <= lines.length; line++) { var prefix = _prefix; @@ -193,6 +210,20 @@ extension FileHitMapsFormatter on Map { } } + // Add uncovered files if allowed + for (final file in uncoveredFiles) { + if (includeUncovered != null && !includeUncovered(file)) continue; + final source = resolver.resolve(file); + if (source == null || !pathFilter(source)) continue; + + final lines = await loader.load(source); + if (lines == null) continue; + buf.writeln(source); + for (final line in lines) { + buf.writeln(' |$line'); + } + } + return buf.toString(); } } @@ -221,3 +252,4 @@ _PathFilter _getPathFilter({List? reportOn, Set? ignoreGlobs}) { return true; }; } + diff --git a/pkgs/coverage/lib/src/resolver.dart b/pkgs/coverage/lib/src/resolver.dart index cb5b728894..263d2c17dc 100644 --- a/pkgs/coverage/lib/src/resolver.dart +++ b/pkgs/coverage/lib/src/resolver.dart @@ -9,6 +9,19 @@ import 'package:path/path.dart' as p; /// [Resolver] resolves imports with respect to a given environment. class Resolver { + /// Returns a list of all Dart files in the project. + List listAllDartFiles({String directoryPath = '.'}) { + final dir = Directory(directoryPath); + if (!dir.existsSync()) return []; + + return dir + .listSync(recursive: true) + .whereType() + .where((file) => file.path.endsWith('.dart')) + .map((file) => file.path) + .toList(); + } + @Deprecated('Use Resolver.create') Resolver({this.packagesPath, this.sdkRoot}) : _packages = packagesPath != null ? _parsePackages(packagesPath) : null, diff --git a/pkgs/coverage/test/run_and_collect_test.dart b/pkgs/coverage/test/run_and_collect_test.dart index e371f90005..6714b81744 100644 --- a/pkgs/coverage/test/run_and_collect_test.dart +++ b/pkgs/coverage/test/run_and_collect_test.dart @@ -69,6 +69,10 @@ class ThrowingResolver implements Resolver { @override String? get sdkRoot => throw UnimplementedError(); + + @override + List listAllDartFiles({String directoryPath = '.'}) => + throw UnimplementedError(); } void checkIgnoredLinesInFilesCache( From f7a6cc89e3c0ec1d4c8014e6d00cfb4f6916abcb Mon Sep 17 00:00:00 2001 From: Yash Date: Thu, 6 Mar 2025 23:51:16 +0530 Subject: [PATCH 2/3] Updated documentation for collect function regarding isolate groups #533 --- pkgs/coverage/lib/src/collect.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkgs/coverage/lib/src/collect.dart b/pkgs/coverage/lib/src/collect.dart index 76227bac9f..47341931d2 100644 --- a/pkgs/coverage/lib/src/collect.dart +++ b/pkgs/coverage/lib/src/collect.dart @@ -42,8 +42,9 @@ const _debugTokenPositions = bool.fromEnvironment('DEBUG_COVERAGE'); /// If [scopedOutput] is non-empty, coverage will be restricted so that only /// scripts that start with any of the provided paths are considered. /// -/// If [isolateIds] is set, the coverage gathering will be restricted to only -/// those VM isolates. +/// If [isolateIds] is set, coverage gathering **will not be restricted** to +/// only those VM isolates. Instead, coverage will be collected for **all isolates +/// in the same isolate group** as the provided isolate(s). /// /// If [coverableLineCache] is set, the collector will avoid recompiling /// libraries it has already seen (see VmService.getSourceReport's From d126883cc036a065f9eea1613e9c7d3ca662e6be Mon Sep 17 00:00:00 2001 From: Yash Date: Fri, 7 Mar 2025 00:42:49 +0530 Subject: [PATCH 3/3] Refined filtering functionality for collect function #529 --- pkgs/coverage/lib/src/collect.dart | 25 ++++++++++++++++++------- pkgs/coverage/lib/src/resolver.dart | 27 +++++++++++++++------------ 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/pkgs/coverage/lib/src/collect.dart b/pkgs/coverage/lib/src/collect.dart index 47341931d2..afc5038672 100644 --- a/pkgs/coverage/lib/src/collect.dart +++ b/pkgs/coverage/lib/src/collect.dart @@ -43,7 +43,8 @@ const _debugTokenPositions = bool.fromEnvironment('DEBUG_COVERAGE'); /// scripts that start with any of the provided paths are considered. /// /// If [isolateIds] is set, coverage gathering **will not be restricted** to -/// only those VM isolates. Instead, coverage will be collected for **all isolates +/// only those VM isolates. Instead, coverage will be collected for +/// **all isolates /// in the same isolate group** as the provided isolate(s). /// /// If [coverableLineCache] is set, the collector will avoid recompiling @@ -57,11 +58,12 @@ const _debugTokenPositions = bool.fromEnvironment('DEBUG_COVERAGE'); Future> collect(Uri serviceUri, bool resume, bool waitPaused, bool includeDart, Set? scopedOutput, {Set? isolateIds, - Duration? timeout, - bool functionCoverage = false, - bool branchCoverage = false, - Map>? coverableLineCache, - VmService? serviceOverrideForTesting}) async { + Duration? timeout, + bool functionCoverage = false, + bool branchCoverage = false, + Map>? coverableLineCache, + VmService? serviceOverrideForTesting, + bool Function(String)? filter}) async { // Correct function type scopedOutput ??= {}; late VmService service; @@ -95,7 +97,7 @@ Future> collect(Uri serviceUri, bool resume, } try { - return await _getAllCoverage( + final coverageData = await _getAllCoverage( service, includeDart, functionCoverage, @@ -104,6 +106,13 @@ Future> collect(Uri serviceUri, bool resume, isolateIds, coverableLineCache, waitPaused); + + // Apply filtering if a filter function is provided + if (filter != null) { + coverageData.removeWhere((key, value) => !filter(key)); + } + + return coverageData; } finally { if (resume && !waitPaused) { await _resumeIsolates(service); @@ -114,6 +123,8 @@ Future> collect(Uri serviceUri, bool resume, } } + + Future> _getAllCoverage( VmService service, bool includeDart, diff --git a/pkgs/coverage/lib/src/resolver.dart b/pkgs/coverage/lib/src/resolver.dart index 263d2c17dc..21961882a7 100644 --- a/pkgs/coverage/lib/src/resolver.dart +++ b/pkgs/coverage/lib/src/resolver.dart @@ -9,6 +9,21 @@ import 'package:path/path.dart' as p; /// [Resolver] resolves imports with respect to a given environment. class Resolver { + + @Deprecated('Use Resolver.create') + + Resolver + ({this.packagesPath, this.sdkRoot}) + : _packages = packagesPath != null ? _parsePackages(packagesPath) : null, + packagePath = null; + + Resolver._ + ( + {this.packagesPath, + this.packagePath, + this.sdkRoot, + Map? packages}) + : _packages = packages; /// Returns a list of all Dart files in the project. List listAllDartFiles({String directoryPath = '.'}) { final dir = Directory(directoryPath); @@ -22,18 +37,6 @@ class Resolver { .toList(); } - @Deprecated('Use Resolver.create') - Resolver({this.packagesPath, this.sdkRoot}) - : _packages = packagesPath != null ? _parsePackages(packagesPath) : null, - packagePath = null; - - Resolver._( - {this.packagesPath, - this.packagePath, - this.sdkRoot, - Map? packages}) - : _packages = packages; - static Future create({ String? packagesPath, String? packagePath,