tool_coverage.dart 6.72 KB
Newer Older
1 2 3 4 5
// Copyright 2019 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';
6
import 'dart:developer';
7
import 'dart:io';
8
import 'dart:isolate';
9

10 11
import 'package:async/async.dart';
import 'package:coverage/coverage.dart';
12
import 'package:flutter_tools/src/context_runner.dart';
13
import 'package:path/path.dart' as path;
14 15 16 17 18
import 'package:pedantic/pedantic.dart';
import 'package:stream_channel/isolate_channel.dart';
import 'package:stream_channel/stream_channel.dart';
import 'package:test_core/src/runner/hack_register_platform.dart' as hack; // ignore: implementation_imports
import 'package:test_core/src/executable.dart' as test; // ignore: implementation_imports
19
import 'package:vm_service_client/vm_service_client.dart'; // ignore: deprecated_member_use
20 21 22 23 24 25 26
import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
import 'package:test_core/src/runner/platform.dart'; // ignore: implementation_imports
import 'package:test_core/src/runner/runner_suite.dart'; // ignore: implementation_imports
import 'package:test_core/src/runner/suite.dart'; // ignore: implementation_imports
import 'package:test_core/src/runner/plugin/platform_helpers.dart'; // ignore: implementation_imports
import 'package:test_core/src/runner/environment.dart'; // ignore: implementation_imports
27 28
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/test/coverage_collector.dart';
29 30

/// Generates an lcov report for the flutter tool unit tests.
31 32 33
///
/// Example invocation:
///
34
///     dart tool/tool_coverage.dart
35
Future<void> main(List<String> arguments) async {
36 37 38 39 40
  return runInContext(() async {
    final VMPlatform vmPlatform = VMPlatform();
    hack.registerPlatformPlugin(
      <Runtime>[Runtime.vm],
      () => vmPlatform,
41
    );
42 43
    await test.main(<String>['-x', 'no_coverage', '--no-color', '-r', 'compact', '-j', '1', ...arguments]);
    exit(exitCode);
44 45 46
  });
}

47 48 49
/// A platform that loads tests in isolates spawned within this Dart process.
class VMPlatform extends PlatformPlugin {
  final CoverageCollector coverageCollector = CoverageCollector(
50
    libraryPredicate: (String libraryName) => libraryName.contains(FlutterProject.current().manifest.appName),
51
  );
52
  final Map<String, Future<void>> _pending = <String, Future<void>>{};
53
  final String precompiledPath = path.join('.dart_tool', 'build', 'generated', 'flutter_tools');
54

55
  @override
56
  StreamChannel<void> loadChannel(String codePath, SuitePlatform platform) =>
57
      throw UnimplementedError();
58

59
  @override
60
  Future<RunnerSuite> load(String codePath, SuitePlatform platform,
61 62 63 64
      SuiteConfiguration suiteConfig, Object message) async {
    final ReceivePort receivePort = ReceivePort();
    Isolate isolate;
    try {
65
      isolate = await _spawnIsolate(codePath, receivePort.sendPort);
66 67 68
    } catch (error) {
      receivePort.close();
      rethrow;
69
    }
70 71 72 73
    final Completer<void> completer = Completer<void>();
    // When this is completed we remove it from the map of pending so we can
    // log the futures that get "stuck".
    unawaited(completer.future.whenComplete(() {
74
      _pending.remove(codePath);
75 76 77 78 79 80
    }));
    final ServiceProtocolInfo info = await Service.controlWebServer(enable: true);
    final dynamic channel = IsolateChannel<Object>.connectReceive(receivePort)
        .transformStream(StreamTransformer<Object, Object>.fromHandlers(handleDone: (EventSink<Object> sink) async {
      try {
        // this will throw if collection fails.
81
        await coverageCollector.collectCoverageIsolate(info.serverUri);
82 83 84 85 86 87 88 89 90 91 92 93
      } finally {
        isolate.kill(priority: Isolate.immediate);
        isolate = null;
        sink.close();
        completer.complete();
      }
    }, handleError: (dynamic error, StackTrace stackTrace, EventSink<Object> sink) {
      isolate.kill(priority: Isolate.immediate);
      isolate = null;
      sink.close();
      completer.complete();
    }));
94

95 96
    VMEnvironment environment;
    final RunnerSuiteController controller = deserializeSuite(
97
      codePath,
98 99 100 101 102 103
      platform,
      suiteConfig,
      environment,
      channel,
      message,
    );
104
    _pending[codePath] = completer.future;
105
    return await controller.suite;
106 107
  }

108 109 110 111
  /// Spawns an isolate and passes it [message].
  ///
  /// This isolate connects an [IsolateChannel] to [message] and sends the
  /// serialized tests over that channel.
112 113
  Future<Isolate> _spawnIsolate(String codePath, SendPort message) async {
    String testPath = path.absolute(path.join(precompiledPath, codePath) + '.vm_test.dart');
114
    testPath = testPath.substring(0, testPath.length - '.dart'.length) + '.vm.app.dill';
115 116
    return await Isolate.spawnUri(path.toUri(testPath), <String>[], message,
      packageConfig: path.toUri('.packages'),
117 118
      checked: true,
    );
119 120
  }

121 122
  @override
  Future<void> close() async {
123
    try {
124
      await Future.wait(_pending.values).timeout(const Duration(minutes: 1));
125
    } on TimeoutException {
126 127 128
      // TODO(jonahwilliams): resolve whether there are any specific tests that
      // get stuck or if it is a general infra issue with how we are collecting
      // coverage.
129 130
      // Log tests that are "Stuck" waiting for coverage.
      print('The following tests timed out waiting for coverage:');
131
      print(_pending.keys.join(', '));
132
    }
133 134 135 136 137 138 139 140 141
    final String packagePath = Directory.current.path;
    final Resolver resolver = Resolver(packagesPath: '.packages');
    final Formatter formatter = LcovFormatter(resolver, reportOn: <String>[
      'lib',
    ], basePath: packagePath);
    final String result = await coverageCollector.finalizeCoverage(
      formatter: formatter,
    );
    final String prefix = Platform.environment['SUBSHARD'] ?? '';
142
    final String outputLcovPath = path.join('coverage', '$prefix.lcov.info');
143 144 145
    File(outputLcovPath)
      ..createSync(recursive: true)
      ..writeAsStringSync(result);
146
  }
147
}
148

149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
class VMEnvironment implements Environment {
  VMEnvironment(this.observatoryUrl, this._isolate);

  @override
  final bool supportsDebugging = false;

  @override
  final Uri observatoryUrl;

  /// The VM service isolate object used to control this isolate.
  final VMIsolateRef _isolate;

  @override
  Uri get remoteDebuggerUrl => null;

  @override
  Stream<void> get onRestart => StreamController<dynamic>.broadcast().stream;

  @override
  CancelableOperation<void> displayPause() {
    final CancelableCompleter<dynamic> completer = CancelableCompleter<dynamic>(onCancel: () => _isolate.resume());

    completer.complete(_isolate.pause().then((dynamic _) => _isolate.onPauseOrResume
        .firstWhere((VMPauseEvent event) => event is VMResumeEvent)));

    return completer.operation;
175 176
  }
}