• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2019 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
6import 'dart:developer';
7import 'dart:io';
8import 'dart:isolate';
9
10import 'package:async/async.dart';
11import 'package:coverage/coverage.dart';
12import 'package:flutter_tools/src/context_runner.dart';
13import 'package:path/path.dart' as path;
14import 'package:pedantic/pedantic.dart';
15import 'package:stream_channel/isolate_channel.dart';
16import 'package:stream_channel/stream_channel.dart';
17import 'package:test_core/src/runner/hack_register_platform.dart' as hack; // ignore: implementation_imports
18import 'package:test_core/src/executable.dart' as test; // ignore: implementation_imports
19import 'package:vm_service_client/vm_service_client.dart'; // ignore: deprecated_member_use
20import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
21import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
22import 'package:test_core/src/runner/platform.dart'; // ignore: implementation_imports
23import 'package:test_core/src/runner/runner_suite.dart'; // ignore: implementation_imports
24import 'package:test_core/src/runner/suite.dart'; // ignore: implementation_imports
25import 'package:test_core/src/runner/plugin/platform_helpers.dart'; // ignore: implementation_imports
26import 'package:test_core/src/runner/environment.dart'; // ignore: implementation_imports
27import 'package:flutter_tools/src/project.dart';
28import 'package:flutter_tools/src/test/coverage_collector.dart';
29
30/// Generates an lcov report for the flutter tool unit tests.
31///
32/// Example invocation:
33///
34///     dart tool/tool_coverage.dart
35Future<void> main(List<String> arguments) async {
36  return runInContext(() async {
37    final VMPlatform vmPlatform = VMPlatform();
38    hack.registerPlatformPlugin(
39      <Runtime>[Runtime.vm],
40      () => vmPlatform,
41    );
42    await test.main(<String>['-x', 'no_coverage', '--no-color', '-r', 'compact', '-j', '1', ...arguments]);
43    exit(exitCode);
44  });
45}
46
47/// A platform that loads tests in isolates spawned within this Dart process.
48class VMPlatform extends PlatformPlugin {
49  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      SuiteConfiguration suiteConfig, Object message) async {
62    final ReceivePort receivePort = ReceivePort();
63    Isolate isolate;
64    try {
65      isolate = await _spawnIsolate(codePath, receivePort.sendPort);
66    } catch (error) {
67      receivePort.close();
68      rethrow;
69    }
70    final Completer<void> completer = Completer<void>();
71    // When this is completed we remove it from the map of pending so we can
72    // log the futures that get "stuck".
73    unawaited(completer.future.whenComplete(() {
74      _pending.remove(codePath);
75    }));
76    final ServiceProtocolInfo info = await Service.controlWebServer(enable: true);
77    final dynamic channel = IsolateChannel<Object>.connectReceive(receivePort)
78        .transformStream(StreamTransformer<Object, Object>.fromHandlers(handleDone: (EventSink<Object> sink) async {
79      try {
80        // this will throw if collection fails.
81        await coverageCollector.collectCoverageIsolate(info.serverUri);
82      } finally {
83        isolate.kill(priority: Isolate.immediate);
84        isolate = null;
85        sink.close();
86        completer.complete();
87      }
88    }, handleError: (dynamic error, StackTrace stackTrace, EventSink<Object> sink) {
89      isolate.kill(priority: Isolate.immediate);
90      isolate = null;
91      sink.close();
92      completer.complete();
93    }));
94
95    VMEnvironment environment;
96    final RunnerSuiteController controller = deserializeSuite(
97      codePath,
98      platform,
99      suiteConfig,
100      environment,
101      channel,
102      message,
103    );
104    _pending[codePath] = completer.future;
105    return await controller.suite;
106  }
107
108  /// Spawns an isolate and passes it [message].
109  ///
110  /// This isolate connects an [IsolateChannel] to [message] and sends the
111  /// serialized tests over that channel.
112  Future<Isolate> _spawnIsolate(String codePath, SendPort message) async {
113    String testPath = path.absolute(path.join(precompiledPath, codePath) + '.vm_test.dart');
114    testPath = testPath.substring(0, testPath.length - '.dart'.length) + '.vm.app.dill';
115    return await Isolate.spawnUri(path.toUri(testPath), <String>[], message,
116      packageConfig: path.toUri('.packages'),
117      checked: true,
118    );
119  }
120
121  @override
122  Future<void> close() async {
123    try {
124      await Future.wait(_pending.values).timeout(const Duration(minutes: 1));
125    } on TimeoutException {
126      // TODO(jonahwilliams): resolve whether there are any specific tests that
127      // get stuck or if it is a general infra issue with how we are collecting
128      // coverage.
129      // Log tests that are "Stuck" waiting for coverage.
130      print('The following tests timed out waiting for coverage:');
131      print(_pending.keys.join(', '));
132    }
133    final String packagePath = Directory.current.path;
134    final Resolver resolver = Resolver(packagesPath: '.packages');
135    final Formatter formatter = LcovFormatter(resolver, reportOn: <String>[
136      'lib',
137    ], basePath: packagePath);
138    final String result = await coverageCollector.finalizeCoverage(
139      formatter: formatter,
140    );
141    final String prefix = Platform.environment['SUBSHARD'] ?? '';
142    final String outputLcovPath = path.join('coverage', '$prefix.lcov.info');
143    File(outputLcovPath)
144      ..createSync(recursive: true)
145      ..writeAsStringSync(result);
146  }
147}
148
149class VMEnvironment implements Environment {
150  VMEnvironment(this.observatoryUrl, this._isolate);
151
152  @override
153  final bool supportsDebugging = false;
154
155  @override
156  final Uri observatoryUrl;
157
158  /// The VM service isolate object used to control this isolate.
159  final VMIsolateRef _isolate;
160
161  @override
162  Uri get remoteDebuggerUrl => null;
163
164  @override
165  Stream<void> get onRestart => StreamController<dynamic>.broadcast().stream;
166
167  @override
168  CancelableOperation<void> displayPause() {
169    final CancelableCompleter<dynamic> completer = CancelableCompleter<dynamic>(onCancel: () => _isolate.resume());
170
171    completer.complete(_isolate.pause().then((dynamic _) => _isolate.onPauseOrResume
172        .firstWhere((VMPauseEvent event) => event is VMResumeEvent)));
173
174    return completer.operation;
175  }
176}
177