|  | // 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'; | 
|  | import 'dart:developer'; | 
|  | import 'dart:io'; | 
|  | import 'dart:isolate'; | 
|  |  | 
|  | import 'package:async/async.dart'; | 
|  | import 'package:coverage/coverage.dart'; | 
|  | import 'package:flutter_tools/src/context_runner.dart'; | 
|  | import 'package:path/path.dart' as path; | 
|  | 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 | 
|  | import 'package:vm_service_client/vm_service_client.dart'; // ignore: deprecated_member_use | 
|  | 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 | 
|  | import 'package:flutter_tools/src/project.dart'; | 
|  | import 'package:flutter_tools/src/test/coverage_collector.dart'; | 
|  |  | 
|  | /// Generates an lcov report for the flutter tool unit tests. | 
|  | /// | 
|  | /// Example invocation: | 
|  | /// | 
|  | ///     dart tool/tool_coverage.dart | 
|  | Future<void> main(List<String> arguments) async { | 
|  | return runInContext(() async { | 
|  | final VMPlatform vmPlatform = VMPlatform(); | 
|  | hack.registerPlatformPlugin( | 
|  | <Runtime>[Runtime.vm], | 
|  | () => vmPlatform, | 
|  | ); | 
|  | await test.main(<String>['-x', 'no_coverage', '--no-color', '-r', 'compact', '-j', '1', ...arguments]); | 
|  | exit(exitCode); | 
|  | }); | 
|  | } | 
|  |  | 
|  | /// A platform that loads tests in isolates spawned within this Dart process. | 
|  | class VMPlatform extends PlatformPlugin { | 
|  | final CoverageCollector coverageCollector = CoverageCollector( | 
|  | libraryPredicate: (String libraryName) => libraryName.contains(FlutterProject.current().manifest.appName), | 
|  | ); | 
|  | final Map<String, Future<void>> _pending = <String, Future<void>>{}; | 
|  | final String precompiledPath = path.join('.dart_tool', 'build', 'generated', 'flutter_tools'); | 
|  |  | 
|  | @override | 
|  | StreamChannel<void> loadChannel(String codePath, SuitePlatform platform) => | 
|  | throw UnimplementedError(); | 
|  |  | 
|  | @override | 
|  | Future<RunnerSuite> load(String codePath, SuitePlatform platform, | 
|  | SuiteConfiguration suiteConfig, Object message) async { | 
|  | final ReceivePort receivePort = ReceivePort(); | 
|  | Isolate isolate; | 
|  | try { | 
|  | isolate = await _spawnIsolate(codePath, receivePort.sendPort); | 
|  | } catch (error) { | 
|  | receivePort.close(); | 
|  | rethrow; | 
|  | } | 
|  | 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(() { | 
|  | _pending.remove(codePath); | 
|  | })); | 
|  | 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. | 
|  | await coverageCollector.collectCoverageIsolate(info.serverUri); | 
|  | } 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(); | 
|  | })); | 
|  |  | 
|  | VMEnvironment environment; | 
|  | final RunnerSuiteController controller = deserializeSuite( | 
|  | codePath, | 
|  | platform, | 
|  | suiteConfig, | 
|  | environment, | 
|  | channel, | 
|  | message, | 
|  | ); | 
|  | _pending[codePath] = completer.future; | 
|  | return await controller.suite; | 
|  | } | 
|  |  | 
|  | /// Spawns an isolate and passes it [message]. | 
|  | /// | 
|  | /// This isolate connects an [IsolateChannel] to [message] and sends the | 
|  | /// serialized tests over that channel. | 
|  | Future<Isolate> _spawnIsolate(String codePath, SendPort message) async { | 
|  | String testPath = path.absolute(path.join(precompiledPath, codePath) + '.vm_test.dart'); | 
|  | testPath = testPath.substring(0, testPath.length - '.dart'.length) + '.vm.app.dill'; | 
|  | return await Isolate.spawnUri(path.toUri(testPath), <String>[], message, | 
|  | packageConfig: path.toUri('.packages'), | 
|  | checked: true, | 
|  | ); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Future<void> close() async { | 
|  | try { | 
|  | await Future.wait(_pending.values).timeout(const Duration(minutes: 1)); | 
|  | } on TimeoutException { | 
|  | // 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. | 
|  | // Log tests that are "Stuck" waiting for coverage. | 
|  | print('The following tests timed out waiting for coverage:'); | 
|  | print(_pending.keys.join(', ')); | 
|  | } | 
|  | 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'] ?? ''; | 
|  | final String outputLcovPath = path.join('coverage', '$prefix.lcov.info'); | 
|  | File(outputLcovPath) | 
|  | ..createSync(recursive: true) | 
|  | ..writeAsStringSync(result); | 
|  | } | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  | } |