| Index: lib/src/runner/plugin/platform.dart
|
| diff --git a/lib/src/runner/plugin/platform.dart b/lib/src/runner/plugin/platform.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..be5a14ee0bdaa243c99e0fbb6fd29a92bdf81924
|
| --- /dev/null
|
| +++ b/lib/src/runner/plugin/platform.dart
|
| @@ -0,0 +1,180 @@
|
| +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
|
| +// for details. 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 'package:stack_trace/stack_trace.dart';
|
| +import 'package:stream_channel/stream_channel.dart';
|
| +
|
| +import '../../backend/group.dart';
|
| +import '../../backend/metadata.dart';
|
| +import '../../backend/test.dart';
|
| +import '../../backend/test_platform.dart';
|
| +import '../../util/io.dart';
|
| +import '../../util/remote_exception.dart';
|
| +import '../environment.dart';
|
| +import '../load_exception.dart';
|
| +import '../runner_suite.dart';
|
| +import '../runner_test.dart';
|
| +import 'environment.dart';
|
| +
|
| +/// A class that defines a platform for which test suites can be loaded.
|
| +///
|
| +/// A minimal plugin must define [platforms], which indicates the platforms it
|
| +/// supports, and [loadChannel], which connects to a client in which the tests
|
| +/// are defined. This is enough to support most of the test runner's
|
| +/// functionality.
|
| +///
|
| +/// In order to support interactive debugging, a plugin must override [load] as
|
| +/// well, which returns a [RunnerSuite] that can contain a custom [Environment]
|
| +/// and control debugging metadata such as [RunnerSuite.isDebugging] and
|
| +/// [RunnerSuite.onDebugging]. To make this easier, implementations can call
|
| +/// [deserializeSuite].
|
| +///
|
| +/// A platform plugin can be registered with [Loader.registerPlatformPlugin].
|
| +abstract class PlatformPlugin {
|
| + /// The platforms supported by this plugin.
|
| + ///
|
| + /// A plugin may declare support for existing platform, in which case it
|
| + /// overrides the previous loading functionality for that platform.
|
| + List<TestPlatform> get platforms;
|
| +
|
| + /// Loads and establishes a connection with the test file at [path] using
|
| + /// [platform].
|
| + ///
|
| + /// This returns a channel that's connected to a remote client. The client
|
| + /// must connect it to a channel returned by [serializeGroup]. The default
|
| + /// implementation of [load] will take care of wrapping it up in a
|
| + /// [RunnerSuite] and running the tests when necessary.
|
| + ///
|
| + /// The returned channel may emit exceptions, indicating that the suite failed
|
| + /// to load or crashed later on. If the channel is closed by the caller, that
|
| + /// indicates that the suite is no longer needed and its resources may be
|
| + /// released.
|
| + ///
|
| + /// The [platform] is guaranteed to be a member of [platforms].
|
| + StreamChannel loadChannel(String path, TestPlatform platform);
|
| +
|
| + /// Loads the runner suite for the test file at [path] using [platform], with
|
| + /// [metadata] parsed from the test file's top-level annotations.
|
| + ///
|
| + /// By default, this just calls [loadChannel] and passes its result to
|
| + /// [deserializeSuite]. However, it can be overridden to provide more
|
| + /// fine-grained control over the [RunnerSuite], including providing a custom
|
| + /// implementation of [Environment].
|
| + ///
|
| + /// It's recommended that subclasses overriding this method call
|
| + /// [deserializeSuite] to obtain a [RunnerSuiteController].
|
| + Future<RunnerSuite> load(String path, TestPlatform platform,
|
| + Metadata metadata) async {
|
| + // loadChannel may throw an exception. That's fine; it will cause the
|
| + // LoadSuite to emit an error, which will be presented to the user.
|
| + var channel = loadChannel(path, platform);
|
| + var controller = await deserializeSuite(
|
| + path, platform, metadata, new PluginEnvironment(), channel);
|
| + return controller.suite;
|
| + }
|
| +
|
| + /// A helper method for creating a [RunnerSuiteController] containing tests
|
| + /// that communicate over [channel].
|
| + ///
|
| + /// This is notionally a protected method. It may be called by subclasses, but
|
| + /// it shouldn't be accessed by externally.
|
| + ///
|
| + /// This returns a controller so that the caller has a chance to control the
|
| + /// runner suite's debugging state based on plugin-specific logic.
|
| + Future<RunnerSuiteController> deserializeSuite(String path,
|
| + TestPlatform platform, Metadata metadata, Environment environment,
|
| + StreamChannel channel) async {
|
| + var disconnector = new Disconnector();
|
| + var suiteChannel = new MultiChannel(channel.transform(disconnector));
|
| +
|
| + suiteChannel.sink.add({
|
| + 'platform': platform.identifier,
|
| + 'metadata': metadata.serialize(),
|
| + 'os': platform == TestPlatform.vm ? currentOS.name : null
|
| + });
|
| +
|
| + var completer = new Completer();
|
| +
|
| + handleError(error, stackTrace) {
|
| + disconnector.disconnect();
|
| +
|
| + if (completer.isCompleted) {
|
| + // If we've already provided a controller, send the error to the
|
| + // LoadSuite. This will cause the virtual load test to fail, which will
|
| + // notify the user of the error.
|
| + Zone.current.handleUncaughtError(error, stackTrace);
|
| + } else {
|
| + completer.completeError(error, stackTrace);
|
| + }
|
| + }
|
| +
|
| + suiteChannel.stream.listen((response) {
|
| + switch (response["type"]) {
|
| + case "print":
|
| + print(response["line"]);
|
| + break;
|
| +
|
| + case "loadException":
|
| + handleError(
|
| + new LoadException(path, response["message"]),
|
| + new Trace.current());
|
| + break;
|
| +
|
| + case "error":
|
| + var asyncError = RemoteException.deserialize(response["error"]);
|
| + handleError(
|
| + new LoadException(path, asyncError.error),
|
| + asyncError.stackTrace);
|
| + break;
|
| +
|
| + case "success":
|
| + completer.complete(
|
| + _deserializeGroup(suiteChannel, response["root"]));
|
| + break;
|
| + }
|
| + }, onError: handleError, onDone: () {
|
| + if (completer.isCompleted) return;
|
| + completer.completeError(
|
| + new LoadException(
|
| + path, "Connection closed before test suite loaded."),
|
| + new Trace.current());
|
| + });
|
| +
|
| + return new RunnerSuiteController(
|
| + environment,
|
| + await completer.future,
|
| + path: path,
|
| + platform: platform,
|
| + os: currentOS,
|
| + onClose: disconnector.disconnect);
|
| + }
|
| +
|
| + /// Deserializes [group] into a concrete [Group].
|
| + Group _deserializeGroup(MultiChannel suiteChannel, Map group) {
|
| + var metadata = new Metadata.deserialize(group['metadata']);
|
| + return new Group(group['name'], group['entries'].map((entry) {
|
| + if (entry['type'] == 'group') {
|
| + return _deserializeGroup(suiteChannel, entry);
|
| + }
|
| +
|
| + return _deserializeTest(suiteChannel, entry);
|
| + }),
|
| + metadata: metadata,
|
| + setUpAll: _deserializeTest(suiteChannel, group['setUpAll']),
|
| + tearDownAll: _deserializeTest(suiteChannel, group['tearDownAll']));
|
| + }
|
| +
|
| + /// Deserializes [test] into a concrete [Test] class.
|
| + ///
|
| + /// Returns `null` if [test] is `null`.
|
| + Test _deserializeTest(MultiChannel suiteChannel, Map test) {
|
| + if (test == null) return null;
|
| +
|
| + var metadata = new Metadata.deserialize(test['metadata']);
|
| + var testChannel = suiteChannel.virtualChannel(test['channel']);
|
| + return new RunnerTest(test['name'], metadata, testChannel);
|
| + }
|
| +}
|
|
|