coverage_collector.dart 3.67 KB
Newer Older
1 2 3 4 5 6
// 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';

7
import 'package:coverage/coverage.dart' as coverage;
8

9 10
import '../base/file_system.dart';
import '../base/io.dart';
11
import '../dart/package_map.dart';
12 13
import '../globals.dart';

14 15
import 'watcher.dart';

16
/// A class that's used to collect coverage data during tests.
17
class CoverageCollector extends TestWatcher {
18 19
  Map<String, dynamic> _globalHitmap;

20 21 22 23 24 25
  @override
  Future<Null> onFinishedTests(ProcessEvent event) async {
    printTrace('test ${event.childIndex}: collecting coverage');
    await collectCoverage(event.process, event.observatoryUri);
  }

26
  void _addHitmap(Map<String, dynamic> hitmap) {
27 28 29
    if (_globalHitmap == null)
      _globalHitmap = hitmap;
    else
30
      coverage.mergeHitmaps(hitmap, _globalHitmap);
31 32
  }

33
  /// Collects coverage for the given [Process] using the given `port`.
34
  ///
35 36 37 38
  /// 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.
39
  Future<Null> collectCoverage(Process process, Uri observatoryUri) async {
40
    assert(process != null);
41
    assert(observatoryUri != null);
42

43
    final int pid = process.pid;
44
    int exitCode;
45
    process.exitCode.then<Null>((int code) {
46 47
      exitCode = code;
    });
48 49
    if (exitCode != null)
      throw new Exception('Failed to collect coverage, process terminated before coverage could be collected.');
50

51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
    printTrace('pid $pid: collecting coverage data from $observatoryUri...');
    final Map<String, dynamic> data = await coverage
        .collect(observatoryUri, false, false)
        .timeout(
          const Duration(seconds: 30),
          onTimeout: () {
            throw new Exception('Failed to collect coverage, it took more than thirty seconds.');
          },
        );
    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();
    }());
68
    if (exitCode != null)
69 70
      throw new Exception('Failed to collect coverage, process terminated while coverage was being collected.');
    _addHitmap(coverage.createHitmap(data['coverage']));
71
    printTrace('pid $pid ($observatoryUri): done merging coverage data into global coverage map.');
72 73
  }

74 75 76
  /// Returns a future that will complete with the formatted coverage data
  /// (using [formatter]) once all coverage data has been collected.
  ///
77 78
  /// This will not start any collection tasks. It us up to the caller of to
  /// call [collectCoverage] for each process first.
79 80 81 82
  ///
  /// If [timeout] is specified, the future will timeout (with a
  /// [TimeoutException]) after the specified duration.
  Future<String> finalizeCoverage({
83
    coverage.Formatter formatter,
84 85
    Duration timeout,
  }) async {
86
    printTrace('formating coverage data');
87 88
    if (_globalHitmap == null)
      return null;
89
    if (formatter == null) {
90 91 92
      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')];
93
      formatter = new coverage.LcovFormatter(resolver, reportOn: reportOn, basePath: packagePath);
94
    }
95
    final String result = await formatter.format(_globalHitmap);
96 97
    _globalHitmap = null;
    return result;
98 99
  }
}