mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
177 lines
6.6 KiB
Dart
177 lines
6.6 KiB
Dart
// 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 p;
|
|
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';
|
|
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(
|
|
flutterProject: FlutterProject.current(),
|
|
);
|
|
final Map<String, Future<void>> _pending = <String, Future<void>>{};
|
|
final String precompiledPath = p.join('.dart_tool', 'build', 'generated', 'flutter_tools');
|
|
|
|
@override
|
|
StreamChannel<void> loadChannel(String path, SuitePlatform platform) =>
|
|
throw UnimplementedError();
|
|
|
|
@override
|
|
Future<RunnerSuite> load(String path, SuitePlatform platform,
|
|
SuiteConfiguration suiteConfig, Object message) async {
|
|
final ReceivePort receivePort = ReceivePort();
|
|
Isolate isolate;
|
|
try {
|
|
isolate = await _spawnIsolate(path, 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(path);
|
|
}));
|
|
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(
|
|
path,
|
|
platform,
|
|
suiteConfig,
|
|
environment,
|
|
channel,
|
|
message,
|
|
);
|
|
_pending[path] = 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 path, SendPort message) async {
|
|
String testPath = p.absolute(p.join(precompiledPath, path) + '.vm_test.dart');
|
|
testPath = testPath.substring(0, testPath.length - '.dart'.length) + '.vm.app.dill';
|
|
return await Isolate.spawnUri(p.toUri(testPath), <String>[], message,
|
|
packageConfig: p.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" waiuting for coverage.
|
|
print('The folllowing 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 = p.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;
|
|
}
|
|
}
|