| // 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 'package:coverage/coverage.dart' as coverage; |
| |
| import '../base/file_system.dart'; |
| import '../base/io.dart'; |
| import '../dart/package_map.dart'; |
| import '../globals.dart'; |
| |
| import 'watcher.dart'; |
| |
| /// A class that's used to collect coverage data during tests. |
| class CoverageCollector extends TestWatcher { |
| Map<String, dynamic> _globalHitmap; |
| |
| @override |
| Future<Null> onFinishedTests(ProcessEvent event) async { |
| printTrace('test ${event.childIndex}: collecting coverage'); |
| await collectCoverage(event.process, event.observatoryUri); |
| } |
| |
| void _addHitmap(Map<String, dynamic> hitmap) { |
| if (_globalHitmap == null) |
| _globalHitmap = hitmap; |
| else |
| coverage.mergeHitmaps(hitmap, _globalHitmap); |
| } |
| |
| /// Collects coverage for the given [Process] using the given `port`. |
| /// |
| /// This should be called when the code whose coverage data is being collected |
| /// has been run to completion so that all coverage data has been recorded. |
| /// |
| /// The returned [Future] completes when the coverage is collected. |
| Future<Null> collectCoverage(Process process, Uri observatoryUri) async { |
| assert(process != null); |
| assert(observatoryUri != null); |
| |
| final int pid = process.pid; |
| int exitCode; |
| // Synchronization is enforced by the API contract. Error handling |
| // synchronization is done in the code below where `exitCode` is checked. |
| // Callback cannot throw. |
| process.exitCode.then<Null>((int code) { // ignore: unawaited_futures |
| exitCode = code; |
| }); |
| if (exitCode != null) |
| throw new Exception('Failed to collect coverage, process terminated before coverage could be collected.'); |
| |
| printTrace('pid $pid: collecting coverage data from $observatoryUri...'); |
| final Map<String, dynamic> data = await coverage |
| .collect(observatoryUri, false, false) |
| .timeout( |
| const Duration(minutes: 2), |
| onTimeout: () { |
| throw new Exception('Timed out while collecting coverage.'); |
| }, |
| ); |
| printTrace(() { |
| final StringBuffer buf = new StringBuffer() |
| ..write('pid $pid ($observatoryUri): ') |
| ..write(exitCode == null |
| ? 'collected coverage data; merging...' |
| : 'process terminated prematurely with exit code $exitCode; aborting'); |
| return buf.toString(); |
| }()); |
| if (exitCode != null) |
| throw new Exception('Failed to collect coverage, process terminated while coverage was being collected.'); |
| _addHitmap(coverage.createHitmap(data['coverage'])); |
| printTrace('pid $pid ($observatoryUri): done merging coverage data into global coverage map.'); |
| } |
| |
| /// Returns a future that will complete with the formatted coverage data |
| /// (using [formatter]) once all coverage data has been collected. |
| /// |
| /// This will not start any collection tasks. It us up to the caller of to |
| /// call [collectCoverage] for each process first. |
| /// |
| /// If [timeout] is specified, the future will timeout (with a |
| /// [TimeoutException]) after the specified duration. |
| Future<String> finalizeCoverage({ |
| coverage.Formatter formatter, |
| Duration timeout, |
| }) async { |
| printTrace('formating coverage data'); |
| if (_globalHitmap == null) |
| return null; |
| if (formatter == null) { |
| final coverage.Resolver resolver = new coverage.Resolver(packagesPath: PackageMap.globalPackagesPath); |
| final String packagePath = fs.currentDirectory.path; |
| final List<String> reportOn = <String>[fs.path.join(packagePath, 'lib')]; |
| formatter = new coverage.LcovFormatter(resolver, reportOn: reportOn, basePath: packagePath); |
| } |
| final String result = await formatter.format(_globalHitmap); |
| _globalHitmap = null; |
| return result; |
| } |
| } |