Skip to content

Commit 1e94649

Browse files
coverage: Allow specifying coverage flags via a yaml file (#1954)
1 parent 933f5ea commit 1e94649

16 files changed

+544
-49
lines changed

pkgs/coverage/.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ build
1515
# Temp files
1616
*~
1717
coverage/
18-
var/
18+
var/

pkgs/coverage/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 1.12.0-wip
2+
3+
- Introduced support for specifying coverage flags through a YAML file.
4+
15
## 1.11.1
26

37
- Update `package:vm_service` constraints to '>=12.0.0 <16.0.0'.

pkgs/coverage/bin/collect_coverage.dart

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import 'dart:io';
88

99
import 'package:args/args.dart';
1010
import 'package:coverage/src/collect.dart';
11+
import 'package:coverage/src/coverage_options.dart';
1112
import 'package:logging/logging.dart';
13+
import 'package:meta/meta.dart';
14+
import 'package:path/path.dart' as p;
1215
import 'package:stack_trace/stack_trace.dart';
1316

1417
Future<void> main(List<String> arguments) async {
@@ -17,15 +20,19 @@ Future<void> main(List<String> arguments) async {
1720
print('${rec.level.name}: ${rec.time}: ${rec.message}');
1821
});
1922

20-
final options = _parseArgs(arguments);
23+
final defaultOptions = CoverageOptionsProvider().coverageOptions;
24+
final options = parseArgs(arguments, defaultOptions);
25+
26+
final out = options.out == null ? stdout : File(options.out!).openWrite();
27+
2128
await Chain.capture(() async {
2229
final coverage = await collect(options.serviceUri, options.resume,
2330
options.waitPaused, options.includeDart, options.scopedOutput,
2431
timeout: options.timeout,
2532
functionCoverage: options.functionCoverage,
2633
branchCoverage: options.branchCoverage);
27-
options.out.write(json.encode(coverage));
28-
await options.out.close();
34+
out.write(json.encode(coverage));
35+
await out.close();
2936
}, onError: (dynamic error, Chain chain) {
3037
stderr.writeln(error);
3138
stderr.writeln(chain.terse);
@@ -48,7 +55,7 @@ class Options {
4855
this.scopedOutput);
4956

5057
final Uri serviceUri;
51-
final IOSink out;
58+
final String? out;
5259
final Duration? timeout;
5360
final bool waitPaused;
5461
final bool resume;
@@ -58,7 +65,8 @@ class Options {
5865
final Set<String> scopedOutput;
5966
}
6067

61-
Options _parseArgs(List<String> arguments) {
68+
@visibleForTesting
69+
Options parseArgs(List<String> arguments, CoverageOptions defaultOptions) {
6270
final parser = ArgParser()
6371
..addOption('host',
6472
abbr: 'H',
@@ -69,11 +77,11 @@ Options _parseArgs(List<String> arguments) {
6977
help: 'remote VM port. DEPRECATED: use --uri',
7078
defaultsTo: '8181')
7179
..addOption('uri', abbr: 'u', help: 'VM observatory service URI')
72-
..addOption('out',
73-
abbr: 'o', defaultsTo: 'stdout', help: 'output: may be file or stdout')
80+
..addOption('out', abbr: 'o', help: 'output: may be file or stdout')
7481
..addOption('connect-timeout',
7582
abbr: 't', help: 'connect timeout in seconds')
7683
..addMultiOption('scope-output',
84+
defaultsTo: defaultOptions.scopeOutput,
7785
help: 'restrict coverage results so that only scripts that start with '
7886
'the provided package path are considered')
7987
..addFlag('wait-paused',
@@ -85,10 +93,12 @@ Options _parseArgs(List<String> arguments) {
8593
..addFlag('include-dart',
8694
abbr: 'd', defaultsTo: false, help: 'include "dart:" libraries')
8795
..addFlag('function-coverage',
88-
abbr: 'f', defaultsTo: false, help: 'Collect function coverage info')
96+
abbr: 'f',
97+
defaultsTo: defaultOptions.functionCoverage,
98+
help: 'Collect function coverage info')
8999
..addFlag('branch-coverage',
90100
abbr: 'b',
91-
defaultsTo: false,
101+
defaultsTo: defaultOptions.branchCoverage,
92102
help: 'Collect branch coverage info (Dart VM must also be run with '
93103
'--branch-coverage for this to work)')
94104
..addFlag('help', abbr: 'h', negatable: false, help: 'show this help');
@@ -125,13 +135,24 @@ Options _parseArgs(List<String> arguments) {
125135
}
126136

127137
final scopedOutput = args['scope-output'] as List<String>;
128-
IOSink out;
129-
if (args['out'] == 'stdout') {
130-
out = stdout;
138+
String? out;
139+
final outPath = args['out'] as String?;
140+
if (outPath == 'stdout' ||
141+
(outPath == null && defaultOptions.outputDirectory == null)) {
142+
out = null;
131143
} else {
132-
final outfile = File(args['out'] as String)..createSync(recursive: true);
133-
out = outfile.openWrite();
144+
final outFilePath = p.normalize(outPath ??
145+
p.absolute(defaultOptions.outputDirectory!, 'coverage.json'));
146+
147+
final outFile = File(outFilePath);
148+
if (!FileSystemEntity.isDirectorySync(outFilePath) &&
149+
!FileSystemEntity.isFileSync(outFilePath)) {
150+
outFile.createSync(recursive: true);
151+
}
152+
153+
out = outFile.path;
134154
}
155+
135156
final timeout = (args['connect-timeout'] == null)
136157
? null
137158
: Duration(seconds: int.parse(args['connect-timeout'] as String));

pkgs/coverage/bin/format_coverage.dart

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:io';
66

77
import 'package:args/args.dart';
88
import 'package:coverage/coverage.dart';
9+
import 'package:coverage/src/coverage_options.dart';
910
import 'package:glob/glob.dart';
1011
import 'package:path/path.dart' as p;
1112

@@ -37,7 +38,7 @@ class Environment {
3738
bool checkIgnore;
3839
String input;
3940
bool lcov;
40-
IOSink output;
41+
String? output;
4142
String? packagesPath;
4243
String packagePath;
4344
bool prettyPrint;
@@ -51,7 +52,8 @@ class Environment {
5152
}
5253

5354
Future<void> main(List<String> arguments) async {
54-
final env = parseArgs(arguments);
55+
final defaultOptions = CoverageOptionsProvider().coverageOptions;
56+
final env = parseArgs(arguments, defaultOptions);
5557

5658
final files = filesToProcess(env.input);
5759
if (env.verbose) {
@@ -104,8 +106,11 @@ Future<void> main(List<String> arguments) async {
104106
basePath: env.baseDirectory);
105107
}
106108

107-
env.output.write(output);
108-
await env.output.flush();
109+
final outputSink =
110+
env.output == null ? stdout : File(env.output!).openWrite();
111+
112+
outputSink.write(output);
113+
await outputSink.flush();
109114
if (env.verbose) {
110115
print('Done flushing output. Took ${clock.elapsedMilliseconds} ms.');
111116
}
@@ -124,26 +129,43 @@ Future<void> main(List<String> arguments) async {
124129
}
125130
}
126131
}
127-
await env.output.close();
132+
await outputSink.close();
128133
}
129134

130135
/// Checks the validity of the provided arguments. Does not initialize actual
131136
/// processing.
132-
Environment parseArgs(List<String> arguments) {
137+
Environment parseArgs(List<String> arguments, CoverageOptions defaultOptions) {
133138
final parser = ArgParser();
134139

135140
parser
136-
..addOption('sdk-root', abbr: 's', help: 'path to the SDK root')
137-
..addOption('packages', help: '[DEPRECATED] path to the package spec file')
141+
..addOption(
142+
'sdk-root',
143+
abbr: 's',
144+
help: 'path to the SDK root',
145+
)
146+
..addOption(
147+
'packages',
148+
help: '[DEPRECATED] path to the package spec file',
149+
)
138150
..addOption('package',
139-
help: 'root directory of the package', defaultsTo: '.')
140-
..addOption('in', abbr: 'i', help: 'input(s): may be file or directory')
141-
..addOption('out',
142-
abbr: 'o', defaultsTo: 'stdout', help: 'output: may be file or stdout')
143-
..addMultiOption('report-on',
144-
help: 'which directories or files to report coverage on')
145-
..addOption('workers',
146-
abbr: 'j', defaultsTo: '1', help: 'number of workers')
151+
help: 'root directory of the package',
152+
defaultsTo: defaultOptions.packageDirectory)
153+
..addOption(
154+
'in',
155+
abbr: 'i',
156+
help: 'input(s): may be file or directory',
157+
)
158+
..addOption('out', abbr: 'o', help: 'output: may be file or stdout')
159+
..addMultiOption(
160+
'report-on',
161+
help: 'which directories or files to report coverage on',
162+
)
163+
..addOption(
164+
'workers',
165+
abbr: 'j',
166+
defaultsTo: '1',
167+
help: 'number of workers',
168+
)
147169
..addOption('bazel-workspace',
148170
defaultsTo: '', help: 'Bazel workspace directory')
149171
..addOption('base-directory',
@@ -220,20 +242,31 @@ Environment parseArgs(List<String> arguments) {
220242
fail('Package spec "${args["package"]}" not found, or not a directory.');
221243
}
222244

223-
if (args['in'] == null) fail('No input files given.');
224-
final input = p.absolute(p.normalize(args['in'] as String));
245+
if (args['in'] == null && defaultOptions.outputDirectory == null) {
246+
fail('No input files given.');
247+
}
248+
final input = p.normalize((args['in'] as String?) ??
249+
p.absolute(defaultOptions.outputDirectory!, 'coverage.json'));
225250
if (!FileSystemEntity.isDirectorySync(input) &&
226251
!FileSystemEntity.isFileSync(input)) {
227252
fail('Provided input "${args["in"]}" is neither a directory nor a file.');
228253
}
229254

230-
IOSink output;
231-
if (args['out'] == 'stdout') {
232-
output = stdout;
255+
String? output;
256+
final outPath = args['out'] as String?;
257+
if (outPath == 'stdout' ||
258+
(outPath == null && defaultOptions.outputDirectory == null)) {
259+
output = null;
233260
} else {
234-
final outpath = p.absolute(p.normalize(args['out'] as String));
235-
final outfile = File(outpath)..createSync(recursive: true);
236-
output = outfile.openWrite();
261+
final outFilePath = p.normalize(
262+
outPath ?? p.absolute(defaultOptions.outputDirectory!, 'lcov.info'));
263+
264+
final outfile = File(outFilePath);
265+
if (!FileSystemEntity.isDirectorySync(outFilePath) &&
266+
!FileSystemEntity.isFileSync(outFilePath)) {
267+
outfile.createSync(recursive: true);
268+
}
269+
output = outfile.path;
237270
}
238271

239272
final reportOnRaw = args['report-on'] as List<String>;

pkgs/coverage/bin/test_with_coverage.dart

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import 'dart:async';
66
import 'dart:io';
77

88
import 'package:args/args.dart';
9+
import 'package:coverage/src/coverage_options.dart';
910
import 'package:coverage/src/util.dart'
1011
show StandardOutExtension, extractVMServiceUri;
12+
import 'package:meta/meta.dart';
1113
import 'package:package_config/package_config.dart';
1214
import 'package:path/path.dart' as path;
1315

@@ -50,37 +52,41 @@ void _watchExitSignal(ProcessSignal signal) {
5052
});
5153
}
5254

53-
ArgParser _createArgParser() => ArgParser()
55+
ArgParser _createArgParser(CoverageOptions defaultOptions) => ArgParser()
5456
..addOption(
5557
'package',
5658
help: 'Root directory of the package to test.',
57-
defaultsTo: '.',
59+
defaultsTo: defaultOptions.packageDirectory,
5860
)
5961
..addOption(
6062
'package-name',
6163
help: 'Name of the package to test. '
6264
'Deduced from --package if not provided.',
65+
defaultsTo: defaultOptions.packageName,
6366
)
6467
..addOption('port', help: 'VM service port.', defaultsTo: '8181')
6568
..addOption(
6669
'out',
70+
defaultsTo: defaultOptions.outputDirectory,
6771
abbr: 'o',
6872
help: 'Output directory. Defaults to <package-dir>/coverage.',
6973
)
70-
..addOption('test', help: 'Test script to run.', defaultsTo: 'test')
74+
..addOption('test',
75+
help: 'Test script to run.', defaultsTo: defaultOptions.testScript)
7176
..addFlag(
7277
'function-coverage',
7378
abbr: 'f',
74-
defaultsTo: false,
79+
defaultsTo: defaultOptions.functionCoverage,
7580
help: 'Collect function coverage info.',
7681
)
7782
..addFlag(
7883
'branch-coverage',
7984
abbr: 'b',
80-
defaultsTo: false,
85+
defaultsTo: defaultOptions.branchCoverage,
8186
help: 'Collect branch coverage info.',
8287
)
8388
..addMultiOption('scope-output',
89+
defaultsTo: defaultOptions.scopeOutput,
8490
help: 'restrict coverage results so that only scripts that start with '
8591
'the provided package path are considered. Defaults to the name of '
8692
'the package under test.')
@@ -110,8 +116,10 @@ class Flags {
110116
final List<String> rest;
111117
}
112118

113-
Future<Flags> _parseArgs(List<String> arguments) async {
114-
final parser = _createArgParser();
119+
@visibleForTesting
120+
Future<Flags> parseArgs(
121+
List<String> arguments, CoverageOptions defaultOptions) async {
122+
final parser = _createArgParser(defaultOptions);
115123
final args = parser.parse(arguments);
116124

117125
void printUsage() {
@@ -138,7 +146,7 @@ ${parser.usage}
138146
exit(0);
139147
}
140148

141-
final packageDir = path.canonicalize(args['package'] as String);
149+
final packageDir = path.normalize(path.absolute(args['package'] as String));
142150
if (!FileSystemEntity.isDirectorySync(packageDir)) {
143151
fail('--package is not a valid directory.');
144152
}
@@ -166,7 +174,8 @@ ${parser.usage}
166174
}
167175

168176
Future<void> main(List<String> arguments) async {
169-
final flags = await _parseArgs(arguments);
177+
final defaultOptions = CoverageOptionsProvider().coverageOptions;
178+
final flags = await parseArgs(arguments, defaultOptions);
170179
final outJson = path.join(flags.outDir, 'coverage.json');
171180
final outLcov = path.join(flags.outDir, 'lcov.info');
172181

0 commit comments

Comments
 (0)