Chromium Code Reviews| 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) { |
|
kevmoo
2016/02/11 19:57:49
Couldn't be done with `await for`?
nweiz
2016/02/11 21:29:18
No; we don't want to block the method on the strea
|
| + 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); |
| + } |
| +} |