Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

coverage: Allow specifying coverage flags via a yaml file #1954

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pkgs/coverage/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.11.2
Dhruv-Maradiya marked this conversation as resolved.
Show resolved Hide resolved

- Introduced support for specifying coverage flags through a YAML file.

## 1.11.1

- Update `package:vm_service` constraints to '>=12.0.0 <16.0.0'.
Expand Down
21 changes: 15 additions & 6 deletions pkgs/coverage/bin/collect_coverage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'dart:io';

import 'package:args/args.dart';
import 'package:coverage/src/collect.dart';
import 'package:coverage/src/coverage_options.dart';
import 'package:logging/logging.dart';
import 'package:stack_trace/stack_trace.dart';

Expand All @@ -17,7 +18,8 @@ Future<void> main(List<String> arguments) async {
print('${rec.level.name}: ${rec.time}: ${rec.message}');
});

final options = _parseArgs(arguments);
final defaultOptions = CoverageOptionsProvider().coverageOptions;
final options = _parseArgs(arguments, defaultOptions);
await Chain.capture(() async {
final coverage = await collect(options.serviceUri, options.resume,
options.waitPaused, options.includeDart, options.scopedOutput,
Expand Down Expand Up @@ -58,7 +60,7 @@ class Options {
final Set<String> scopedOutput;
}

Options _parseArgs(List<String> arguments) {
Options _parseArgs(List<String> arguments, CoverageOptions defaultOptions) {
final parser = ArgParser()
..addOption('host',
abbr: 'H',
Expand All @@ -70,10 +72,15 @@ Options _parseArgs(List<String> arguments) {
defaultsTo: '8181')
..addOption('uri', abbr: 'u', help: 'VM observatory service URI')
..addOption('out',
abbr: 'o', defaultsTo: 'stdout', help: 'output: may be file or stdout')
abbr: 'o',
defaultsTo: defaultOptions.output,
help: 'output: may be file or stdout')
..addOption('connect-timeout',
abbr: 't', help: 'connect timeout in seconds')
defaultsTo: defaultOptions.connectTimeout?.toString(),
abbr: 't',
help: 'connect timeout in seconds')
..addMultiOption('scope-output',
defaultsTo: defaultOptions.scopeOutput,
help: 'restrict coverage results so that only scripts that start with '
'the provided package path are considered')
..addFlag('wait-paused',
Expand All @@ -85,10 +92,12 @@ Options _parseArgs(List<String> arguments) {
..addFlag('include-dart',
abbr: 'd', defaultsTo: false, help: 'include "dart:" libraries')
..addFlag('function-coverage',
abbr: 'f', defaultsTo: false, help: 'Collect function coverage info')
abbr: 'f',
defaultsTo: defaultOptions.functionCoverage,
help: 'Collect function coverage info')
..addFlag('branch-coverage',
abbr: 'b',
defaultsTo: false,
defaultsTo: defaultOptions.branchCoverage,
help: 'Collect branch coverage info (Dart VM must also be run with '
'--branch-coverage for this to work)')
..addFlag('help', abbr: 'h', negatable: false, help: 'show this help');
Expand Down
51 changes: 39 additions & 12 deletions pkgs/coverage/bin/format_coverage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dart:io';

import 'package:args/args.dart';
import 'package:coverage/coverage.dart';
import 'package:coverage/src/coverage_options.dart';
import 'package:glob/glob.dart';
import 'package:path/path.dart' as p;

Expand Down Expand Up @@ -51,7 +52,8 @@ class Environment {
}

Future<void> main(List<String> arguments) async {
final env = parseArgs(arguments);
final defaultOptions = CoverageOptionsProvider().coverageOptions;
final env = parseArgs(arguments, defaultOptions);

final files = filesToProcess(env.input);
if (env.verbose) {
Expand Down Expand Up @@ -129,30 +131,50 @@ Future<void> main(List<String> arguments) async {

/// Checks the validity of the provided arguments. Does not initialize actual
/// processing.
Environment parseArgs(List<String> arguments) {
Environment parseArgs(List<String> arguments, CoverageOptions defaultOptions) {
final parser = ArgParser();

parser
..addOption('sdk-root', abbr: 's', help: 'path to the SDK root')
..addOption('packages', help: '[DEPRECATED] path to the package spec file')
..addOption(
'sdk-root',
abbr: 's',
help: 'path to the SDK root',
)
..addOption(
'packages',
help: '[DEPRECATED] path to the package spec file',
)
..addOption('package',
help: 'root directory of the package', defaultsTo: '.')
..addOption('in', abbr: 'i', help: 'input(s): may be file or directory')
help: 'root directory of the package',
defaultsTo: defaultOptions.packagePath)
..addOption('in',
abbr: 'i',
help: 'input(s): may be file or directory',
defaultsTo: defaultOptions.input)
..addOption('out',
abbr: 'o', defaultsTo: 'stdout', help: 'output: may be file or stdout')
abbr: 'o',
defaultsTo: defaultOptions.output,
help: 'output: may be file or stdout')
..addMultiOption('report-on',
help: 'which directories or files to report coverage on')
..addOption('workers',
abbr: 'j', defaultsTo: '1', help: 'number of workers')
help: 'which directories or files to report coverage on',
defaultsTo: defaultOptions.reportOn)
..addOption(
'workers',
abbr: 'j',
defaultsTo: defaultOptions.workers.toString(),
help: 'number of workers',
)
..addOption('bazel-workspace',
defaultsTo: '', help: 'Bazel workspace directory')
..addOption('base-directory',
abbr: 'b',
defaultsTo: defaultOptions.baseDirectory,
help: 'the base directory relative to which source paths are output')
..addFlag('bazel',
defaultsTo: false, help: 'use Bazel-style path resolution')
..addFlag('pretty-print',
abbr: 'r',
defaultsTo: defaultOptions.prettyPrint,
negatable: false,
help: 'convert line coverage data to pretty print format')
..addFlag('pretty-print-func',
Expand All @@ -164,9 +186,14 @@ Environment parseArgs(List<String> arguments) {
help: 'convert branch coverage data to pretty print format')
..addFlag('lcov',
abbr: 'l',
defaultsTo: defaultOptions.lcov,
negatable: false,
help: 'convert coverage data to lcov format')
..addFlag('verbose', abbr: 'v', negatable: false, help: 'verbose output')
..addFlag('verbose',
abbr: 'v',
defaultsTo: defaultOptions.verbose,
negatable: false,
help: 'verbose output')
..addFlag(
'check-ignore',
abbr: 'c',
Expand All @@ -176,7 +203,7 @@ Environment parseArgs(List<String> arguments) {
)
..addMultiOption(
'ignore-files',
defaultsTo: [],
defaultsTo: defaultOptions.ignoreFiles,
help: 'Ignore files by glob patterns',
)
..addFlag('help', abbr: 'h', negatable: false, help: 'show this help');
Expand Down
36 changes: 28 additions & 8 deletions pkgs/coverage/bin/test_with_coverage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dart:async';
import 'dart:io';

import 'package:args/args.dart';
import 'package:coverage/src/coverage_options.dart';
import 'package:coverage/src/util.dart'
show StandardOutExtension, extractVMServiceUri;
import 'package:package_config/package_config.dart';
Expand Down Expand Up @@ -50,37 +51,41 @@ void _watchExitSignal(ProcessSignal signal) {
});
}

ArgParser _createArgParser() => ArgParser()
ArgParser _createArgParser(CoverageOptions defaultOptions) => ArgParser()
..addOption(
'package',
help: 'Root directory of the package to test.',
defaultsTo: '.',
defaultsTo: defaultOptions.packageName,
)
..addOption(
'package-name',
help: 'Name of the package to test. '
'Deduced from --package if not provided.',
defaultsTo: defaultOptions.packageName,
)
..addOption('port', help: 'VM service port.', defaultsTo: '8181')
..addOption(
'out',
defaultsTo: defaultOptions.output,
abbr: 'o',
help: 'Output directory. Defaults to <package-dir>/coverage.',
)
..addOption('test', help: 'Test script to run.', defaultsTo: 'test')
..addOption('test',
help: 'Test script to run.', defaultsTo: defaultOptions.testScript)
..addFlag(
'function-coverage',
abbr: 'f',
defaultsTo: false,
defaultsTo: defaultOptions.functionCoverage,
help: 'Collect function coverage info.',
)
..addFlag(
'branch-coverage',
abbr: 'b',
defaultsTo: false,
defaultsTo: defaultOptions.branchCoverage,
help: 'Collect branch coverage info.',
)
..addMultiOption('scope-output',
defaultsTo: defaultOptions.scopeOutput,
help: 'restrict coverage results so that only scripts that start with '
'the provided package path are considered. Defaults to the name of '
'the package under test.')
Expand Down Expand Up @@ -110,8 +115,9 @@ class Flags {
final List<String> rest;
}

Future<Flags> _parseArgs(List<String> arguments) async {
final parser = _createArgParser();
Future<Flags> _parseArgs(
List<String> arguments, CoverageOptions defaultOptions) async {
final parser = _createArgParser(defaultOptions);
final args = parser.parse(arguments);

void printUsage() {
Expand Down Expand Up @@ -166,7 +172,21 @@ ${parser.usage}
}

Future<void> main(List<String> arguments) async {
final flags = await _parseArgs(arguments);
final defaultOptions = CoverageOptionsProvider().coverageOptions;
final flags = await _parseArgs(arguments, defaultOptions);

print('Flags: ');
Dhruv-Maradiya marked this conversation as resolved.
Show resolved Hide resolved
print(' packageDir: ${flags.packageDir}');
print(' packageName: ${flags.packageName}');
print(' outDir: ${flags.outDir}');
print(' port: ${flags.port}');
print(' testScript: ${flags.testScript}');
print(' functionCoverage: ${flags.functionCoverage}');
print(' branchCoverage: ${flags.branchCoverage}');
print(' scopeOutput: ${flags.scopeOutput}');
print(' rest: ${flags.rest}');
print('');

final outJson = path.join(flags.outDir, 'coverage.json');
final outLcov = path.join(flags.outDir, 'lcov.info');

Expand Down
124 changes: 124 additions & 0 deletions pkgs/coverage/lib/src/coverage_options.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import 'dart:io';
import 'package:cli_config/cli_config.dart';

class CoverageOptions {
const CoverageOptions({
required this.output,
Dhruv-Maradiya marked this conversation as resolved.
Show resolved Hide resolved
this.connectTimeout,
required this.scopeOutput,
required this.resumeIsolates,
required this.functionCoverage,
required this.branchCoverage,
required this.packagePath,
this.input,
this.reportOn,
required this.workers,
this.baseDirectory,
required this.prettyPrint,
required this.lcov,
required this.ignoreFiles,
this.packageName,
required this.testScript,
required this.verbose,
});

factory CoverageOptions.fromConfig(
Config options, CoverageOptions defaultOptions) {
return CoverageOptions(
Dhruv-Maradiya marked this conversation as resolved.
Show resolved Hide resolved
output: options.optionalString('out') ?? defaultOptions.output,
Dhruv-Maradiya marked this conversation as resolved.
Show resolved Hide resolved
connectTimeout: options.optionalInt('connect_timeout') ??
defaultOptions.connectTimeout,
scopeOutput: options.optionalStringList('scope_output') ??
defaultOptions.scopeOutput,
resumeIsolates: options.optionalBool('resume_isolates') ??
defaultOptions.resumeIsolates,
functionCoverage: options.optionalBool('function_coverage') ??
defaultOptions.functionCoverage,
branchCoverage: options.optionalBool('branch_coverage') ??
defaultOptions.branchCoverage,
packagePath:
options.optionalString('package') ?? defaultOptions.packagePath,
Dhruv-Maradiya marked this conversation as resolved.
Show resolved Hide resolved
input: options.optionalString('in') ?? defaultOptions.input,
reportOn:
options.optionalStringList('report_on') ?? defaultOptions.reportOn,
workers: options.optionalInt('workers') ?? defaultOptions.workers,
baseDirectory: options.optionalString('base_directory') ??
defaultOptions.baseDirectory,
prettyPrint:
options.optionalBool('pretty_print') ?? defaultOptions.prettyPrint,
lcov: options.optionalBool('lcov') ?? defaultOptions.lcov,
ignoreFiles: options.optionalStringList('ignore_files') ??
defaultOptions.ignoreFiles,
packageName:
options.optionalString('package_name') ?? defaultOptions.packageName,
testScript: options.optionalString('test') ?? defaultOptions.testScript,
verbose: options.optionalBool('verbose') ?? defaultOptions.verbose,
);
}

final String output;
final int? connectTimeout;
final List<String> scopeOutput;
final bool resumeIsolates;
final bool functionCoverage;
final bool branchCoverage;
final String packagePath;
final String? input;
final List<String>? reportOn;
final int workers;
final String? baseDirectory;
final bool prettyPrint;
final bool lcov;
final List<String> ignoreFiles;
final String? packageName;
final String testScript;
final bool verbose;
}

class CoverageOptionsProvider {
CoverageOptionsProvider({
String? filePath,
}) {
final file = _getOptionsFile(filePath ?? CoverageOptionsProvider.filePath);
Dhruv-Maradiya marked this conversation as resolved.
Show resolved Hide resolved
final fileContents = file?.readAsStringSync();

final isFileEmpty = fileContents?.isEmpty ?? false;

// Pass null to fileContents if the file is empty
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a question: Why do you need to pass null to fileContents if the file is empty?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we pass empty file content to fromConfigFileContents, it throws a FormatException. Should we also handle cases where the file is empty but contains only blank spaces?

final options = Config.fromConfigFileContents(
fileContents: isFileEmpty ? null : fileContents,
fileSourceUri: file?.uri,
);

coverageOptions = CoverageOptions.fromConfig(options, defaultOptions);
}

late final CoverageOptions coverageOptions;

static const filePath = 'coverage.yaml';
Dhruv-Maradiya marked this conversation as resolved.
Show resolved Hide resolved

static File? _getOptionsFile(String filePath) {
final file = File(filePath);
return file.existsSync() ? file : null;
}

static const defaultOptions = CoverageOptions(
output: 'stdout',
Dhruv-Maradiya marked this conversation as resolved.
Show resolved Hide resolved
connectTimeout: null,
scopeOutput: [],
resumeIsolates: false,
functionCoverage: false,
branchCoverage: false,
packagePath: '.',
input: null,
reportOn: null,
workers: 1,
baseDirectory: '.',
prettyPrint: false,
lcov: false,
ignoreFiles: [],
packageName: null,
testScript: 'test',
verbose: false,
);
}
Loading
Loading