Unverified Commit 1bf6f023 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] support coverage and machine together (#51988)

parent bbf913bc
...@@ -208,24 +208,21 @@ class TestCommand extends FlutterCommand { ...@@ -208,24 +208,21 @@ class TestCommand extends FlutterCommand {
]; ];
} }
final bool machine = boolArg('machine');
CoverageCollector collector; CoverageCollector collector;
if (boolArg('coverage') || boolArg('merge-coverage')) { if (boolArg('coverage') || boolArg('merge-coverage')) {
final String projectName = FlutterProject.current().manifest.appName; final String projectName = FlutterProject.current().manifest.appName;
collector = CoverageCollector( collector = CoverageCollector(
verbose: !machine,
libraryPredicate: (String libraryName) => libraryName.contains(projectName), libraryPredicate: (String libraryName) => libraryName.contains(projectName),
); );
} }
final bool machine = boolArg('machine');
if (collector != null && machine) {
throwToolExit("The test command doesn't support --machine and coverage together");
}
TestWatcher watcher; TestWatcher watcher;
if (collector != null) { if (machine) {
watcher = EventPrinter(parent: collector);
} else if (collector != null) {
watcher = collector; watcher = collector;
} else if (machine) {
watcher = EventPrinter();
} }
Cache.releaseLockEarly(); Cache.releaseLockEarly();
......
...@@ -8,7 +8,6 @@ import 'package:coverage/coverage.dart' as coverage; ...@@ -8,7 +8,6 @@ import 'package:coverage/coverage.dart' as coverage;
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/logger.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../dart/package_map.dart'; import '../dart/package_map.dart';
...@@ -19,17 +18,29 @@ import 'watcher.dart'; ...@@ -19,17 +18,29 @@ import 'watcher.dart';
/// A class that's used to collect coverage data during tests. /// A class that's used to collect coverage data during tests.
class CoverageCollector extends TestWatcher { class CoverageCollector extends TestWatcher {
CoverageCollector({this.libraryPredicate}); CoverageCollector({this.libraryPredicate, this.verbose = true});
final bool verbose;
Map<String, Map<int, int>> _globalHitmap; Map<String, Map<int, int>> _globalHitmap;
bool Function(String) libraryPredicate; bool Function(String) libraryPredicate;
@override @override
Future<void> handleFinishedTest(ProcessEvent event) async { Future<void> handleFinishedTest(ProcessEvent event) async {
globals.printTrace('test ${event.childIndex}: collecting coverage'); _logMessage('test ${event.childIndex}: collecting coverage');
await collectCoverage(event.process, event.observatoryUri); await collectCoverage(event.process, event.observatoryUri);
} }
void _logMessage(String line, { bool error = false }) {
if (!verbose) {
return;
}
if (error) {
globals.printError(line);
} else {
globals.printTrace(line);
}
}
void _addHitmap(Map<String, Map<int, int>> hitmap) { void _addHitmap(Map<String, Map<int, int>> hitmap) {
if (_globalHitmap == null) { if (_globalHitmap == null) {
_globalHitmap = hitmap; _globalHitmap = hitmap;
...@@ -46,16 +57,16 @@ class CoverageCollector extends TestWatcher { ...@@ -46,16 +57,16 @@ class CoverageCollector extends TestWatcher {
/// The returned [Future] completes when the coverage is collected. /// The returned [Future] completes when the coverage is collected.
Future<void> collectCoverageIsolate(Uri observatoryUri) async { Future<void> collectCoverageIsolate(Uri observatoryUri) async {
assert(observatoryUri != null); assert(observatoryUri != null);
print('collecting coverage data from $observatoryUri...'); _logMessage('collecting coverage data from $observatoryUri...');
final Map<String, dynamic> data = await collect(observatoryUri, libraryPredicate); final Map<String, dynamic> data = await collect(observatoryUri, libraryPredicate);
if (data == null) { if (data == null) {
throw Exception('Failed to collect coverage.'); throw Exception('Failed to collect coverage.');
} }
assert(data != null); assert(data != null);
print('($observatoryUri): collected coverage data; merging...'); _logMessage('($observatoryUri): collected coverage data; merging...');
_addHitmap(coverage.createHitmap(data['coverage'] as List<Map<String, dynamic>>)); _addHitmap(coverage.createHitmap(data['coverage'] as List<Map<String, dynamic>>));
print('($observatoryUri): done merging coverage data into global coverage map.'); _logMessage('($observatoryUri): done merging coverage data into global coverage map.');
} }
/// Collects coverage for the given [Process] using the given `port`. /// Collects coverage for the given [Process] using the given `port`.
...@@ -68,7 +79,7 @@ class CoverageCollector extends TestWatcher { ...@@ -68,7 +79,7 @@ class CoverageCollector extends TestWatcher {
assert(process != null); assert(process != null);
assert(observatoryUri != null); assert(observatoryUri != null);
final int pid = process.pid; final int pid = process.pid;
globals.printTrace('pid $pid: collecting coverage data from $observatoryUri...'); _logMessage('pid $pid: collecting coverage data from $observatoryUri...');
Map<String, dynamic> data; Map<String, dynamic> data;
final Future<void> processComplete = process.exitCode final Future<void> processComplete = process.exitCode
...@@ -85,9 +96,9 @@ class CoverageCollector extends TestWatcher { ...@@ -85,9 +96,9 @@ class CoverageCollector extends TestWatcher {
await Future.any<void>(<Future<void>>[ processComplete, collectionComplete ]); await Future.any<void>(<Future<void>>[ processComplete, collectionComplete ]);
assert(data != null); assert(data != null);
globals.printTrace('pid $pid ($observatoryUri): collected coverage data; merging...'); _logMessage('pid $pid ($observatoryUri): collected coverage data; merging...');
_addHitmap(coverage.createHitmap(data['coverage'] as List<Map<String, dynamic>>)); _addHitmap(coverage.createHitmap(data['coverage'] as List<Map<String, dynamic>>));
globals.printTrace('pid $pid ($observatoryUri): done merging coverage data into global coverage map.'); _logMessage('pid $pid ($observatoryUri): done merging coverage data into global coverage map.');
} }
/// Returns a future that will complete with the formatted coverage data /// Returns a future that will complete with the formatted coverage data
...@@ -116,12 +127,10 @@ class CoverageCollector extends TestWatcher { ...@@ -116,12 +127,10 @@ class CoverageCollector extends TestWatcher {
} }
Future<bool> collectCoverageData(String coveragePath, { bool mergeCoverageData = false, Directory coverageDirectory }) async { Future<bool> collectCoverageData(String coveragePath, { bool mergeCoverageData = false, Directory coverageDirectory }) async {
final Status status = globals.logger.startProgress('Collecting coverage information...', timeout: timeoutConfiguration.fastOperation);
final String coverageData = await finalizeCoverage( final String coverageData = await finalizeCoverage(
coverageDirectory: coverageDirectory, coverageDirectory: coverageDirectory,
); );
status.stop(); _logMessage('coverage information collection complete');
globals.printTrace('coverage information collection complete');
if (coverageData == null) { if (coverageData == null) {
return false; return false;
} }
...@@ -129,12 +138,12 @@ class CoverageCollector extends TestWatcher { ...@@ -129,12 +138,12 @@ class CoverageCollector extends TestWatcher {
final File coverageFile = globals.fs.file(coveragePath) final File coverageFile = globals.fs.file(coveragePath)
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsStringSync(coverageData, flush: true); ..writeAsStringSync(coverageData, flush: true);
globals.printTrace('wrote coverage data to $coveragePath (size=${coverageData.length})'); _logMessage('wrote coverage data to $coveragePath (size=${coverageData.length})');
const String baseCoverageData = 'coverage/lcov.base.info'; const String baseCoverageData = 'coverage/lcov.base.info';
if (mergeCoverageData) { if (mergeCoverageData) {
if (!globals.fs.isFileSync(baseCoverageData)) { if (!globals.fs.isFileSync(baseCoverageData)) {
globals.printError('Missing "$baseCoverageData". Unable to merge coverage data.'); _logMessage('Missing "$baseCoverageData". Unable to merge coverage data.', error: true);
return false; return false;
} }
...@@ -145,7 +154,7 @@ class CoverageCollector extends TestWatcher { ...@@ -145,7 +154,7 @@ class CoverageCollector extends TestWatcher {
} else if (globals.platform.isMacOS) { } else if (globals.platform.isMacOS) {
installMessage = 'Consider running "brew install lcov".'; installMessage = 'Consider running "brew install lcov".';
} }
globals.printError('Missing "lcov" tool. Unable to merge coverage data.\n$installMessage'); _logMessage('Missing "lcov" tool. Unable to merge coverage data.\n$installMessage', error: true);
return false; return false;
} }
......
...@@ -8,14 +8,18 @@ import 'watcher.dart'; ...@@ -8,14 +8,18 @@ import 'watcher.dart';
/// Prints JSON events when running a test in --machine mode. /// Prints JSON events when running a test in --machine mode.
class EventPrinter extends TestWatcher { class EventPrinter extends TestWatcher {
EventPrinter({StringSink out}) : _out = out ?? globals.stdio.stdout; EventPrinter({StringSink out, TestWatcher parent})
: _out = out ?? globals.stdio.stdout,
_parent = parent;
final StringSink _out; final StringSink _out;
final TestWatcher _parent;
@override @override
void handleStartedProcess(ProcessEvent event) { void handleStartedProcess(ProcessEvent event) {
_sendEvent('test.startedProcess', _sendEvent('test.startedProcess',
<String, dynamic>{'observatoryUri': event.observatoryUri.toString()}); <String, dynamic>{'observatoryUri': event.observatoryUri.toString()});
_parent?.handleStartedProcess(event);
} }
void _sendEvent(String name, [ dynamic params ]) { void _sendEvent(String name, [ dynamic params ]) {
......
...@@ -6,6 +6,7 @@ import 'dart:async'; ...@@ -6,6 +6,7 @@ import 'dart:async';
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
...@@ -54,6 +55,28 @@ void main() { ...@@ -54,6 +55,28 @@ void main() {
Cache: () => FakeCache(), Cache: () => FakeCache(),
}); });
testUsingContext('Supports coverage and machine', () async {
final FakePackageTest fakePackageTest = FakePackageTest();
final TestCommand testCommand = TestCommand(testWrapper: fakePackageTest);
final CommandRunner<void> commandRunner =
createTestCommandRunner(testCommand);
expect(() => commandRunner.run(const <String>[
'test',
'--no-pub',
'--machine',
'--coverage',
'--',
'test/fake_test.dart',
]), throwsA(isA<ToolExit>()
.having((ToolExit toolExit) => toolExit.message, 'message', isNull)));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
Cache: () => FakeCache(),
});
testUsingContext('Pipes start-paused to package:test', testUsingContext('Pipes start-paused to package:test',
() async { () async {
final FakePackageTest fakePackageTest = FakePackageTest(); final FakePackageTest fakePackageTest = FakePackageTest();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment