Commit b094fa6d authored by Adam Barth's avatar Adam Barth Committed by GitHub

Add support for --coverage to flutter test (#4679)

We need https://github.com/dart-lang/coverage/issues/100 to be fixed before
this will be useful.

Fixes #2342
parent 6c0b88aa
......@@ -13,12 +13,21 @@ import '../cache.dart';
import '../dart/package_map.dart';
import '../globals.dart';
import '../runner/flutter_command.dart';
import '../test/coverage_collector.dart';
import '../test/flutter_platform.dart' as loader;
import '../toolchain.dart';
class TestCommand extends FlutterCommand {
TestCommand() {
usesPubOption();
argParser.addFlag('coverage',
defaultsTo: false,
help: 'Whether to collect coverage information.'
);
argParser.addOption('coverage-path',
defaultsTo: 'coverage/lcov.info',
help: 'Where to store coverage information (if coverage is enabled).'
);
}
@override
......@@ -67,6 +76,7 @@ class TestCommand extends FlutterCommand {
printTrace('running test package with arguments: $testArgs');
await executable.main(testArgs);
printTrace('test package returned with exit code $exitCode');
return exitCode;
} finally {
Directory.current = currentDirectory;
......@@ -96,6 +106,9 @@ class TestCommand extends FlutterCommand {
if (!terminal.supportsColor)
testArgs.insert(0, '--no-color');
if (argResults['coverage'])
testArgs.insert(0, '--concurrency=1');
loader.installHook();
loader.shellPath = tools.getHostToolPath(HostTool.SkyShell);
if (!FileSystemEntity.isFileSync(loader.shellPath)) {
......@@ -105,6 +118,23 @@ class TestCommand extends FlutterCommand {
Cache.releaseLockEarly();
return await _runTests(testArgs, testDir);
CoverageCollector collector = CoverageCollector.instance;
collector.enabled = argResults['coverage'];
int result = await _runTests(testArgs, testDir);
if (collector.enabled) {
Status status = logger.startProgress("Collecting coverage information...");
String coverageData = await collector.finalizeCoverage();
status.stop(showElapsedTime: true);
String coveragePath = argResults['coverage-path'];
new File(coveragePath)
..createSync(recursive: true)
..writeAsStringSync(coverageData, flush: true);
printTrace('wrote coverage data to $coveragePath (size=${coverageData.length})');
}
return result;
}
}
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:coverage/coverage.dart';
import 'package:path/path.dart' as path;
import '../globals.dart';
class CoverageCollector {
static final CoverageCollector instance = new CoverageCollector();
bool enabled = false;
void collectCoverage({
String host,
int port,
Process processToKill
}) {
if (enabled) {
assert(_jobs != null);
_jobs.add(_startJob(
host: host,
port: port,
processToKill: processToKill
));
} else {
processToKill.kill();
}
}
Future<Null> _startJob({
String host,
int port,
Process processToKill
}) async {
int pid = processToKill.pid;
printTrace('collecting coverage data from pid $pid on port $port');
Map<String, dynamic> data = await collect(host, port, false, false);
printTrace('done collecting coverage data from pid $pid');
processToKill.kill();
Map<String, dynamic> hitmap = createHitmap(data['coverage']);
if (_globalHitmap == null)
_globalHitmap = hitmap;
else
mergeHitmaps(hitmap, _globalHitmap);
printTrace('done merging data from pid $pid into global coverage map');
}
Future<Null> finishPendingJobs() async {
await Future.wait(_jobs.toList(), eagerError: true);
}
List<Future<Null>> _jobs = <Future<Null>>[];
Map<String, dynamic> _globalHitmap;
Future<String> finalizeCoverage() async {
assert(enabled);
await finishPendingJobs();
printTrace('formating coverage data');
// TODO(abarth): Use PackageMap.globalPackagesPath once
// https://github.com/dart-lang/coverage/issues/100 is fixed.
Resolver resolver = new Resolver(packageRoot: path.absolute('packages'));
Formatter formater = new LcovFormatter(resolver);
List<String> reportOn = <String>[path.join(Directory.current.path, 'lib')];
return await formater.format(_globalHitmap, reportOn: reportOn);
}
}
......@@ -5,6 +5,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math' as math;
import 'package:async/async.dart';
import 'package:path/path.dart' as path;
......@@ -16,6 +17,7 @@ import 'package:test/src/runner/plugin/hack_register_platform.dart' as hack; //
import '../dart/package_map.dart';
import '../globals.dart';
import 'coverage_collector.dart';
final String _kSkyShell = Platform.environment['SKY_SHELL'];
const String _kHost = '127.0.0.1';
......@@ -45,15 +47,20 @@ Future<_ServerInfo> _startServer() async {
return new _ServerInfo(server, 'ws://$_kHost:${server.port}$_kPath', socket.future);
}
Future<Process> _startProcess(String mainPath, { String packages }) {
Future<Process> _startProcess(String mainPath, { String packages, int observatoryPort }) {
assert(shellPath != null || _kSkyShell != null); // Please provide the path to the shell in the SKY_SHELL environment variable.
String executable = shellPath ?? _kSkyShell;
List<String> arguments = <String>[
List<String> arguments = <String>[];
if (observatoryPort != null) {
arguments.add('--observatory-port=$observatoryPort');
} else {
arguments.add('--non-interactive');
}
arguments.addAll(<String>[
'--enable-checked-mode',
'--non-interactive',
'--packages=$packages',
mainPath
];
]);
printTrace('$executable ${arguments.join(' ')}');
return Process.start(executable, arguments, environment: <String, String>{ 'FLUTTER_TEST': 'true' });
}
......@@ -105,8 +112,16 @@ void main() {
}
''');
int observatoryPort;
if (CoverageCollector.instance.enabled) {
observatoryPort = new math.Random().nextInt(30000) + 2000;
await CoverageCollector.instance.finishPendingJobs();
}
Process process = await _startProcess(
listenerFile.path, packages: PackageMap.globalPackagesPath
listenerFile.path,
packages: PackageMap.globalPackagesPath,
observatoryPort: observatoryPort
);
_attachStandardStreams(process);
......@@ -115,7 +130,11 @@ void main() {
if (process != null) {
Process processToKill = process;
process = null;
processToKill.kill();
CoverageCollector.instance.collectCoverage(
host: _kHost,
port: observatoryPort,
processToKill: processToKill
);
}
if (tempDir != null) {
Directory dirToDelete = tempDir;
......
......@@ -10,6 +10,7 @@ environment:
dependencies:
archive: ^1.0.20
args: ^0.13.4
coverage: ^0.7.7
crypto: '>=1.1.1 <3.0.0'
file: ^0.1.0
http: ^0.11.3
......
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