├── doc └── package_config.md ├── .test_config ├── image ├── hybrid.png ├── test1.gif └── test2.gif ├── test ├── common.dart ├── runner │ ├── browser │ │ ├── firefox_html_test.dart │ │ ├── compact_reporter_test.dart │ │ ├── expanded_reporter_test.dart │ │ ├── safari_test.dart │ │ ├── firefox_test.dart │ │ ├── internet_explorer_test.dart │ │ ├── phantom_js_test.dart │ │ ├── chrome_test.dart │ │ └── code_server.dart │ ├── solo_test.dart │ ├── set_up_all_test.dart │ ├── timeout_test.dart │ ├── tear_down_all_test.dart │ └── configuration │ │ └── global_test.dart ├── frontend │ ├── expect_test.dart │ ├── never_called_test.dart │ ├── test_chain_test.dart │ └── timeout_test.dart ├── util │ ├── path_handler_test.dart │ └── one_off_handler_test.dart └── io.dart ├── lib ├── src │ ├── runner │ │ ├── browser │ │ │ ├── static │ │ │ │ ├── favicon.ico │ │ │ │ └── index.html │ │ │ ├── internet_explorer.dart │ │ │ ├── default_settings.dart │ │ │ ├── safari.dart │ │ │ ├── post_message_channel.dart │ │ │ ├── firefox.dart │ │ │ ├── phantom_js.dart │ │ │ └── chrome.dart │ │ ├── application_exception.dart │ │ ├── configuration │ │ │ ├── values.dart │ │ │ ├── runtime_selection.dart │ │ │ ├── runtime_settings.dart │ │ │ ├── custom_runtime.dart │ │ │ └── reporters.dart │ │ ├── plugin │ │ │ ├── environment.dart │ │ │ ├── hack_register_platform.dart │ │ │ ├── customizable_platform.dart │ │ │ ├── remote_platform_helpers.dart │ │ │ └── platform.dart │ │ ├── reporter.dart │ │ ├── vm │ │ │ ├── catch_isolate_errors.dart │ │ │ └── environment.dart │ │ ├── node │ │ │ └── socket_channel.dart │ │ ├── environment.dart │ │ ├── version.dart │ │ ├── spawn_hybrid.dart │ │ ├── load_exception.dart │ │ ├── suite_channel_manager.dart │ │ ├── hybrid_listener.dart │ │ ├── live_suite.dart │ │ ├── console.dart │ │ ├── runner_test.dart │ │ └── compiler_pool.dart │ ├── backend │ │ ├── closed_exception.dart │ │ ├── test.dart │ │ ├── outstanding_callback_counter.dart │ │ ├── message.dart │ │ ├── group_entry.dart │ │ ├── suite_platform.dart │ │ ├── suite.dart │ │ ├── operating_system.dart │ │ ├── group.dart │ │ ├── stack_trace_formatter.dart │ │ ├── platform_selector.dart │ │ └── state.dart │ ├── frontend │ │ ├── retry.dart │ │ ├── skip.dart │ │ ├── test_on.dart │ │ ├── on_platform.dart │ │ ├── tags.dart │ │ ├── format_stack_trace.dart │ │ ├── utils.dart │ │ ├── never_called.dart │ │ ├── async_matcher.dart │ │ ├── throws_matchers.dart │ │ ├── prints_matcher.dart │ │ ├── throws_matcher.dart │ │ └── future_matchers.dart │ ├── util │ │ ├── placeholder.dart │ │ ├── iterable_set.dart │ │ ├── exit_codes.dart │ │ ├── one_off_handler.dart │ │ ├── path_handler.dart │ │ ├── dart.dart │ │ ├── stack_trace_mapper.dart │ │ └── remote_exception.dart │ └── bootstrap │ │ ├── vm.dart │ │ ├── node.dart │ │ └── browser.dart ├── bootstrap │ ├── vm.dart │ ├── node.dart │ └── browser.dart └── dart.js ├── codereview.settings ├── tool ├── README.md └── dartfmt_and_analyze.sh ├── bin └── test.dart ├── .gitignore ├── analysis_options.yaml ├── .travis.yml ├── dart_test.yaml ├── pubspec.yaml ├── LICENSE └── CONTRIBUTING.md /doc/package_config.md: -------------------------------------------------------------------------------- 1 | configuration.md -------------------------------------------------------------------------------- /.test_config: -------------------------------------------------------------------------------- 1 | { 2 | "test_package": true 3 | } -------------------------------------------------------------------------------- /image/hybrid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snitko/test/master/image/hybrid.png -------------------------------------------------------------------------------- /image/test1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snitko/test/master/image/test1.gif -------------------------------------------------------------------------------- /image/test2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snitko/test/master/image/test2.gif -------------------------------------------------------------------------------- /test/common.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | myTest(String name, Function() testFn) => test(name, testFn); 4 | -------------------------------------------------------------------------------- /lib/src/runner/browser/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snitko/test/master/lib/src/runner/browser/static/favicon.ico -------------------------------------------------------------------------------- /codereview.settings: -------------------------------------------------------------------------------- 1 | CODE_REVIEW_SERVER: https://codereview.chromium.org/ 2 | VIEW_VC: https://github.com/dart-lang/test/commit/ 3 | CC_LIST: reviews@dartlang.org -------------------------------------------------------------------------------- /tool/README.md: -------------------------------------------------------------------------------- 1 | `host.dart` is the source for `lib/src/runner/browser/static/host.dart.js`. 2 | 3 | To updated it, run: 4 | 5 | ```console 6 | dart2js tool/host.dart -o lib/src/runner/browser/static/host.dart.js 7 | ``` 8 | -------------------------------------------------------------------------------- /tool/dartfmt_and_analyze.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | pub get || exit $ 6 | 7 | EXIT_CODE=0 8 | dartfmt -n --set-exit-if-changed . || EXIT_CODE=$? 9 | dartanalyzer --fatal-infos --fatal-warnings . || EXIT_CODE=$? 10 | 11 | exit $EXIT_CODE 12 | -------------------------------------------------------------------------------- /bin/test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | export 'package:test/src/executable.dart'; 6 | -------------------------------------------------------------------------------- /lib/bootstrap/vm.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | export '../src/bootstrap/vm.dart'; 6 | -------------------------------------------------------------------------------- /lib/bootstrap/node.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | export '../src/bootstrap/node.dart'; 6 | -------------------------------------------------------------------------------- /lib/bootstrap/browser.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | export '../src/bootstrap/browser.dart'; 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Don’t commit the following directories created by pub. 2 | .buildlog 3 | .dart_tool/ 4 | .pub/ 5 | build/ 6 | packages 7 | 8 | # Or the files created by dart2js. 9 | *.dart.js 10 | *.js_ 11 | *.js.deps 12 | *.js.map 13 | 14 | # Include when developing application packages. 15 | pubspec.lock 16 | .packages 17 | -------------------------------------------------------------------------------- /lib/src/runner/application_exception.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// An expected exception caused by user-controllable circumstances. 6 | class ApplicationException implements Exception { 7 | final String message; 8 | 9 | ApplicationException(this.message); 10 | 11 | String toString() => message; 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/backend/closed_exception.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// An exception thrown by various front-end methods when the test framework has 6 | /// been closed and a test must shut down as soon as possible. 7 | class ClosedException implements Exception { 8 | ClosedException(); 9 | 10 | String toString() => "This test has been closed."; 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/frontend/retry.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// An annotation for marking a test to be retried. 6 | /// 7 | /// A test with retries enabled will be re-run if it fails for a reason 8 | /// other than [TestFailure]. 9 | class Retry { 10 | /// The number of times the test will be retried. 11 | final int count; 12 | 13 | /// Marks a test to be retried. 14 | const Retry(this.count); 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/frontend/skip.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// An annotation for marking a test suite as skipped. 6 | class Skip { 7 | /// The reason the test suite is skipped, or `null` if no reason is given. 8 | final String reason; 9 | 10 | /// Marks a suite as skipped. 11 | /// 12 | /// If [reason] is passed, it's included in the test output as the reason the 13 | /// test is skipped. 14 | const Skip([this.reason]); 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/frontend/test_on.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// An annotation indicating which platforms a test suite supports. 6 | /// 7 | /// For the full syntax of [expression], see [the README][]. 8 | /// 9 | /// [the README]: https://github.com/dart-lang/test/#platform-selector-syntax 10 | class TestOn { 11 | /// The expression specifying the platform. 12 | final String expression; 13 | 14 | const TestOn(this.expression); 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/frontend/on_platform.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// An annotation for platform-specific customizations for a test suite. 6 | /// 7 | /// See [the README][onPlatform]. 8 | /// 9 | /// [onPlatform]: https://github.com/dart-lang/test/blob/master/README.md#platform-specific-configuration 10 | class OnPlatform { 11 | final Map annotationsByPlatform; 12 | 13 | const OnPlatform(this.annotationsByPlatform); 14 | } 15 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | strong-mode: 3 | implicit-casts: false 4 | errors: 5 | # Remove noise from workspace diagnostics lists 6 | todo: ignore 7 | # Upgrade lints to errors that will break Google3 8 | unused_element: error 9 | unused_import: error 10 | unused_local_variable: error 11 | dead_code: error 12 | undefined_getter: ignore 13 | ### Useful to uncomment during development to remove the noise of the many 14 | ### deprecated APIs. 15 | # deprecated_member_use: ignore 16 | linter: 17 | rules: 18 | - await_only_futures 19 | - implementation_imports 20 | - prefer_equal_for_default_values 21 | - prefer_typing_uninitialized_variables 22 | -------------------------------------------------------------------------------- /lib/src/util/placeholder.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// A class that's used as a default argument to detect whether an argument was 6 | /// passed. 7 | /// 8 | /// We use a custom class for this rather than just `const Object()` so that 9 | /// callers can't accidentally pass the placeholder value. 10 | class _Placeholder { 11 | const _Placeholder(); 12 | } 13 | 14 | /// A placeholder to use as a default argument value to detect whether an 15 | /// argument was passed. 16 | const placeholder = const _Placeholder(); 17 | -------------------------------------------------------------------------------- /test/runner/browser/firefox_html_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn("firefox") 6 | import 'dart:html'; 7 | 8 | import 'package:test/test.dart'; 9 | 10 | void main() { 11 | // Regression test for #274. Firefox doesn't compute styles within hidden 12 | // iframes (https://bugzilla.mozilla.org/show_bug.cgi?id=548397), so we have 13 | // to do some special stuff to make sure tests that care about that work. 14 | test("getComputedStyle() works", () { 15 | expect(document.body.getComputedStyle(), isNotNull); 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/frontend/tags.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// An annotation for applying a set of user-defined tags to a test suite. 6 | /// 7 | /// See [the documentation on tagging tests][tagging tests]. 8 | /// 9 | /// [tagging tests]: https://github.com/dart-lang/test/blob/master/README.md#tagging-tests 10 | class Tags { 11 | /// The tags for the test suite. 12 | Set get tags => _tags.toSet(); 13 | 14 | final Iterable _tags; 15 | 16 | /// Applies a set of user-defined tags to a test suite. 17 | const Tags(this._tags); 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/runner/configuration/values.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | import 'dart:math' as math; 7 | 8 | import 'package:glob/glob.dart'; 9 | 10 | /// The default number of test suites to run at once. 11 | /// 12 | /// This defaults to half the available processors, since presumably some of 13 | /// them will be used for the OS and other processes. 14 | final defaultConcurrency = math.max(1, Platform.numberOfProcessors ~/ 2); 15 | 16 | /// The default filename pattern. 17 | /// 18 | /// This is stored here so that we don't have to recompile it multiple times. 19 | final defaultFilename = new Glob("*_test.dart"); 20 | -------------------------------------------------------------------------------- /lib/src/runner/plugin/environment.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:async/async.dart'; 8 | 9 | import '../environment.dart'; 10 | 11 | /// The default environment for platform plugins. 12 | class PluginEnvironment implements Environment { 13 | final supportsDebugging = false; 14 | Stream get onRestart => new StreamController.broadcast().stream; 15 | 16 | const PluginEnvironment(); 17 | 18 | Uri get observatoryUrl => null; 19 | 20 | Uri get remoteDebuggerUrl => null; 21 | 22 | CancelableOperation displayPause() => throw new UnsupportedError( 23 | "PluginEnvironment.displayPause is not supported."); 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/runner/configuration/runtime_selection.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:source_span/source_span.dart'; 6 | 7 | /// A runtime on which the user has chosen to run tests. 8 | class RuntimeSelection { 9 | /// The name of the runtime. 10 | final String name; 11 | 12 | /// The location in the configuration file of this runtime string, or `null` 13 | /// if it was defined outside a configuration file (for example, on the 14 | /// command line). 15 | final SourceSpan span; 16 | 17 | RuntimeSelection(this.name, [this.span]); 18 | 19 | bool operator ==(other) => other is RuntimeSelection && other.name == name; 20 | 21 | int get hashCode => name.hashCode; 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/bootstrap/vm.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import "dart:isolate"; 6 | 7 | import "package:stream_channel/stream_channel.dart"; 8 | 9 | import "../runner/plugin/remote_platform_helpers.dart"; 10 | import "../runner/vm/catch_isolate_errors.dart"; 11 | 12 | /// Bootstraps a vm test to communicate with the test runner. 13 | /// 14 | /// This should NOT be used directly, instead use the `test/pub_serve` 15 | /// transformer which will bootstrap your test and call this method. 16 | void internalBootstrapVmTest(Function getMain(), SendPort sendPort) { 17 | var channel = serializeSuite(() { 18 | catchIsolateErrors(); 19 | return getMain(); 20 | }); 21 | new IsolateChannel.connectSend(sendPort).pipe(channel); 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/runner/reporter.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// An interface for classes that watch the progress of an Engine and report it 6 | /// to the user. 7 | /// 8 | /// A reporter should subscribe to the Engine's events as soon as it's created. 9 | abstract class Reporter { 10 | /// Pauses the reporter's output. 11 | /// 12 | /// Subclasses should buffer any events from the engine while they're paused. 13 | /// They should also ensure that this does nothing if the reporter is already 14 | /// paused. 15 | void pause(); 16 | 17 | /// Resumes the reporter's output after being [paused]. 18 | /// 19 | /// Subclasses should ensure that this does nothing if the reporter isn't 20 | /// paused. 21 | void resume(); 22 | 23 | /// Cancels the reporter's output. 24 | void cancel(); 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/bootstrap/node.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import "../runner/plugin/remote_platform_helpers.dart"; 6 | import "../runner/node/socket_channel.dart"; 7 | import "../util/stack_trace_mapper.dart"; 8 | 9 | /// Bootstraps a browser test to communicate with the test runner. 10 | /// 11 | /// This should NOT be used directly, instead use the `test/pub_serve` 12 | /// transformer which will bootstrap your test and call this method. 13 | void internalBootstrapNodeTest(Function getMain()) { 14 | var channel = serializeSuite(getMain, beforeLoad: () async { 15 | var serialized = await suiteChannel("test.node.mapper").stream.first; 16 | if (serialized == null || serialized is! Map) return; 17 | setStackTraceMapper(StackTraceMapper.deserialize(serialized as Map)); 18 | }); 19 | socketChannel().pipe(channel); 20 | } 21 | -------------------------------------------------------------------------------- /test/runner/browser/compact_reporter_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn("vm") 6 | 7 | import 'package:test_descriptor/test_descriptor.dart' as d; 8 | import 'package:test/test.dart'; 9 | 10 | import '../../io.dart'; 11 | 12 | void main() { 13 | test("prints the platform name when running on multiple platforms", () async { 14 | await d.file("test.dart", """ 15 | import 'dart:async'; 16 | 17 | import 'package:path/path.dart' as p; 18 | import 'package:test/test.dart'; 19 | 20 | void main() { 21 | test("success", () {}); 22 | } 23 | """).create(); 24 | 25 | var test = await runTest( 26 | ["-p", "chrome", "-p", "vm", "-j", "1", "test.dart"], 27 | reporter: "compact"); 28 | 29 | expect(test.stdout, containsInOrder(["[VM]", "[Chrome]"])); 30 | await test.shouldExit(0); 31 | }, tags: 'chrome'); 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/bootstrap/browser.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import "../runner/browser/post_message_channel.dart"; 6 | import "../runner/plugin/remote_platform_helpers.dart"; 7 | import "../util/stack_trace_mapper.dart"; 8 | 9 | /// Bootstraps a browser test to communicate with the test runner. 10 | /// 11 | /// This should NOT be used directly, instead use the `test/pub_serve` 12 | /// transformer which will bootstrap your test and call this method. 13 | void internalBootstrapBrowserTest(Function getMain()) { 14 | var channel = 15 | serializeSuite(getMain, hidePrints: false, beforeLoad: () async { 16 | var serialized = 17 | await suiteChannel("test.browser.mapper").stream.first as Map; 18 | if (serialized == null) return; 19 | setStackTraceMapper(StackTraceMapper.deserialize(serialized)); 20 | }); 21 | postMessageChannel().pipe(channel); 22 | } 23 | -------------------------------------------------------------------------------- /test/runner/browser/expanded_reporter_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn("vm") 6 | 7 | import 'package:test_descriptor/test_descriptor.dart' as d; 8 | 9 | import 'package:test/test.dart'; 10 | 11 | import '../../io.dart'; 12 | 13 | void main() { 14 | test("prints the platform name when running on multiple platforms", () async { 15 | await d.file("test.dart", """ 16 | import 'dart:async'; 17 | 18 | import 'package:test/test.dart'; 19 | 20 | void main() { 21 | test("success", () {}); 22 | } 23 | """).create(); 24 | 25 | var test = await runTest([ 26 | "-r", "expanded", "-p", "chrome", "-p", "vm", "-j", "1", // 27 | "test.dart" 28 | ]); 29 | 30 | expect(test.stdoutStream(), emitsThrough(contains("[VM]"))); 31 | expect(test.stdout, emitsThrough(contains("[Chrome]"))); 32 | await test.shouldExit(0); 33 | }, tags: ['chrome']); 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/frontend/format_stack_trace.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:stack_trace/stack_trace.dart'; 6 | 7 | import '../backend/stack_trace_formatter.dart'; 8 | 9 | /// The default formatter to use for formatting stack traces. 10 | /// 11 | /// This is used in situations where the zone-scoped formatter is unavailable, 12 | /// such as when running via `dart path/to/test.dart'. 13 | final _defaultFormatter = new StackTraceFormatter(); 14 | 15 | /// Converts [stackTrace] to a [Chain] according to the current test's 16 | /// configuration. 17 | /// 18 | /// If [verbose] is `true`, this doesn't fold out irrelevant stack frames. It 19 | /// defaults to the current test's `verbose_trace` configuration. 20 | Chain formatStackTrace(StackTrace stackTrace, {bool verbose}) => 21 | (StackTraceFormatter.current ?? _defaultFormatter) 22 | .formatStackTrace(stackTrace, verbose: verbose); 23 | -------------------------------------------------------------------------------- /lib/src/frontend/utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | /// Returns a [Future] that completes after the [event loop][] has run the given 8 | /// number of [times] (20 by default). 9 | /// 10 | /// [event loop]: https://webdev.dartlang.org/articles/performance/event-loop#darts-event-loop-and-queues 11 | /// 12 | /// Awaiting this approximates waiting until all asynchronous work (other than 13 | /// work that's waiting for external resources) completes. 14 | Future pumpEventQueue({int times}) { 15 | times ??= 20; 16 | if (times == 0) return new Future.value(); 17 | // Use [new Future] future to allow microtask events to finish. The [new 18 | // Future.value] constructor uses scheduleMicrotask itself and would therefore 19 | // not wait for microtask callbacks that are scheduled after invoking this 20 | // method. 21 | return new Future(() => pumpEventQueue(times: times - 1)); 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/runner/configuration/runtime_settings.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:source_span/source_span.dart'; 6 | import 'package:yaml/yaml.dart'; 7 | 8 | import '../plugin/customizable_platform.dart'; 9 | 10 | /// User-defined settings for a built-in test runtime. 11 | class RuntimeSettings { 12 | /// The identifier used to look up the runtime being overridden. 13 | final String identifier; 14 | 15 | /// The location that [identifier] was defined in the configuration file. 16 | final SourceSpan identifierSpan; 17 | 18 | /// The user's settings for this runtime. 19 | /// 20 | /// This is a list of settings, from most global to most specific, that will 21 | /// eventually be merged using [CustomizablePlatform.mergePlatformSettings]. 22 | final List settings; 23 | 24 | RuntimeSettings(this.identifier, this.identifierSpan, List settings) 25 | : settings = new List.unmodifiable(settings); 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/runner/vm/catch_isolate_errors.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:isolate'; 7 | 8 | import 'package:stack_trace/stack_trace.dart'; 9 | 10 | /// Capture any top-level errors (mostly lazy syntax errors, since other are 11 | /// caught below) and report them to the parent isolate. 12 | void catchIsolateErrors() { 13 | var errorPort = new ReceivePort(); 14 | // Treat errors non-fatal because otherwise they'll be double-printed. 15 | Isolate.current.setErrorsFatal(false); 16 | Isolate.current.addErrorListener(errorPort.sendPort); 17 | errorPort.listen((message) { 18 | // Masquerade as an IsolateSpawnException because that's what this would 19 | // be if the error had been detected statically. 20 | var error = new IsolateSpawnException(message[0] as String); 21 | var stackTrace = message[1] == null 22 | ? new Trace([]) 23 | : new Trace.parse(message[1] as String); 24 | Zone.current.handleUncaughtError(error, stackTrace); 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: dart 2 | sudo: required 3 | 4 | dart: 5 | - dev 6 | 7 | env: FORCE_TEST_EXIT=true 8 | 9 | addons: 10 | chrome: stable 11 | 12 | 13 | jobs: 14 | include: 15 | - stage: analyze_and_format 16 | script: tool/dartfmt_and_analyze.sh 17 | - stage: unit_test 18 | script: xvfb-run -s "-screen 0 1024x768x24" pub run test --preset travis --total-shards 5 --shard-index 0 19 | - stage: unit_test 20 | script: xvfb-run -s "-screen 0 1024x768x24" pub run test --preset travis --total-shards 5 --shard-index 1 21 | - stage: unit_test 22 | script: xvfb-run -s "-screen 0 1024x768x24" pub run test --preset travis --total-shards 5 --shard-index 2 23 | - stage: unit_test 24 | script: xvfb-run -s "-screen 0 1024x768x24" pub run test --preset travis --total-shards 5 --shard-index 3 25 | - stage: unit_test 26 | script: xvfb-run -s "-screen 0 1024x768x24" pub run test --preset travis --total-shards 5 --shard-index 4 27 | 28 | # Only building master means that we don't run two builds for each pull request. 29 | branches: 30 | only: [master, /feature\..*/] 31 | 32 | stages: 33 | - analyze_and_format 34 | - unit_test 35 | 36 | cache: 37 | directories: 38 | - $HOME/.pub-cache 39 | -------------------------------------------------------------------------------- /lib/src/runner/browser/internet_explorer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:io'; 7 | 8 | import '../../backend/runtime.dart'; 9 | import '../executable_settings.dart'; 10 | import 'browser.dart'; 11 | import 'default_settings.dart'; 12 | 13 | /// A class for running an instance of Internet Explorer. 14 | /// 15 | /// Any errors starting or running the process are reported through [onExit]. 16 | class InternetExplorer extends Browser { 17 | final name = "Internet Explorer"; 18 | 19 | InternetExplorer(url, {ExecutableSettings settings}) 20 | : super(() => _startBrowser(url, settings)); 21 | 22 | /// Starts a new instance of Internet Explorer open to the given [url], which 23 | /// may be a [Uri] or a [String]. 24 | static Future _startBrowser(url, ExecutableSettings settings) { 25 | settings ??= defaultSettings[Runtime.internetExplorer]; 26 | 27 | return Process.start(settings.executable, 28 | ['-extoff', url.toString()]..addAll(settings.arguments)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/runner/vm/environment.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:async/async.dart'; 8 | import 'package:vm_service_client/vm_service_client.dart'; 9 | 10 | import '../environment.dart'; 11 | 12 | /// The environment in which VM tests are loaded. 13 | class VMEnvironment implements Environment { 14 | final supportsDebugging = true; 15 | final Uri observatoryUrl; 16 | 17 | /// The VM service isolate object used to control this isolate. 18 | final VMIsolateRef _isolate; 19 | 20 | VMEnvironment(this.observatoryUrl, this._isolate); 21 | 22 | Uri get remoteDebuggerUrl => null; 23 | 24 | Stream get onRestart => new StreamController.broadcast().stream; 25 | 26 | CancelableOperation displayPause() { 27 | var completer = new CancelableCompleter(onCancel: () => _isolate.resume()); 28 | 29 | completer.complete(_isolate.pause().then((_) => _isolate.onPauseOrResume 30 | .firstWhere((event) => event is VMResumeEvent))); 31 | 32 | return completer.operation; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/runner/configuration/custom_runtime.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:source_span/source_span.dart'; 6 | import 'package:yaml/yaml.dart'; 7 | 8 | /// A user-defined test runtime, based on an existing runtime but with 9 | /// different configuration. 10 | class CustomRuntime { 11 | /// The human-friendly name of the runtime. 12 | final String name; 13 | 14 | /// The location that [name] was defined in the configuration file. 15 | final SourceSpan nameSpan; 16 | 17 | /// The identifier used to look up the runtime. 18 | final String identifier; 19 | 20 | /// The location that [identifier] was defined in the configuration file. 21 | final SourceSpan identifierSpan; 22 | 23 | /// The identifier of the runtime that this extends. 24 | final String parent; 25 | 26 | /// The location that [parent] was defined in the configuration file. 27 | final SourceSpan parentSpan; 28 | 29 | /// The user's settings for this runtime. 30 | final YamlMap settings; 31 | 32 | CustomRuntime(this.name, this.nameSpan, this.identifier, this.identifierSpan, 33 | this.parent, this.parentSpan, this.settings); 34 | } 35 | -------------------------------------------------------------------------------- /dart_test.yaml: -------------------------------------------------------------------------------- 1 | # Fold frames from helper packages we use in our tests, but not from test 2 | # itself. 3 | fold_stack_frames: 4 | except: 5 | - shelf_test_handler 6 | - stream_channel 7 | - test_descriptor 8 | - test_process 9 | 10 | presets: 11 | # "-P terse-trace" folds frames from test's implementation to make the output 12 | # less verbose when 13 | terse-trace: 14 | fold_stack_frames: 15 | except: [test] 16 | 17 | tags: 18 | browser: 19 | timeout: 2x 20 | 21 | # Browsers can sometimes randomly time out while starting, especially on 22 | # Travis which is pretty slow. Don't retry locally because it makes 23 | # debugging more annoying. 24 | presets: {travis: {retry: 3}} 25 | 26 | dart2js: 27 | add_tags: [browser] 28 | timeout: 2x 29 | 30 | firefox: {add_tags: [dart2js]} 31 | chrome: {add_tags: [dart2js]} 32 | phantomjs: {add_tags: [dart2js]} 33 | 34 | safari: 35 | add_tags: [dart2js] 36 | test_on: mac-os 37 | 38 | ie: 39 | add_tags: [dart2js] 40 | test_on: windows 41 | 42 | # Tests that run pub. These tests may need to be excluded when there are local 43 | # dependency_overrides. 44 | pub: 45 | timeout: 2x 46 | 47 | # Tests that use Node.js. These tests may need to be excluded on systems that 48 | # don't have Node installed. 49 | node: 50 | timeout: 2x 51 | -------------------------------------------------------------------------------- /lib/src/backend/test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:stack_trace/stack_trace.dart'; 6 | 7 | import 'group.dart'; 8 | import 'group_entry.dart'; 9 | import 'live_test.dart'; 10 | import 'metadata.dart'; 11 | import 'suite.dart'; 12 | import 'suite_platform.dart'; 13 | 14 | /// A single test. 15 | /// 16 | /// A test is immutable and stateless, which means that it can't be run 17 | /// directly. To run one, load a live version using [Test.load] and run it using 18 | /// [LiveTest.run]. 19 | abstract class Test implements GroupEntry { 20 | String get name; 21 | 22 | Metadata get metadata; 23 | 24 | Trace get trace; 25 | 26 | /// Loads a live version of this test, which can be used to run it a single 27 | /// time. 28 | /// 29 | /// [suite] is the suite within which this test is being run. If [groups] is 30 | /// passed, it's the list of groups containing this test; otherwise, it 31 | /// defaults to just containing `suite.group`. 32 | LiveTest load(Suite suite, {Iterable groups}); 33 | 34 | Test forPlatform(SuitePlatform platform); 35 | 36 | Test filter(bool callback(Test test)) => callback(this) ? this : null; 37 | } 38 | -------------------------------------------------------------------------------- /test/frontend/expect_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:test/test.dart'; 8 | 9 | import '../utils.dart'; 10 | 11 | void main() { 12 | group("returned Future from expectLater()", () { 13 | test("completes immediately for a sync matcher", () { 14 | expect(expectLater(true, isTrue), completes); 15 | }); 16 | 17 | test("contains the expect failure", () { 18 | expect(expectLater(new Future.value(true), completion(isFalse)), 19 | throwsA(isTestFailure(anything))); 20 | }); 21 | 22 | test("contains an async error", () { 23 | expect(expectLater(new Future.error("oh no"), completion(isFalse)), 24 | throwsA("oh no")); 25 | }); 26 | }); 27 | 28 | group("an async matcher that fails synchronously", () { 29 | test("throws synchronously", () { 30 | // ignore: deprecated_member_use 31 | expect(() => expect(() {}, throws), throwsA(isTestFailure(anything))); 32 | }); 33 | 34 | test("can be used with synchronous operators", () { 35 | // ignore: deprecated_member_use 36 | expect(() {}, isNot(throws)); 37 | }); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /lib/src/runner/browser/default_settings.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:collection'; 6 | 7 | import '../../backend/runtime.dart'; 8 | import '../executable_settings.dart'; 9 | 10 | /// Default settings for starting browser executables. 11 | final defaultSettings = new UnmodifiableMapView({ 12 | Runtime.chrome: new ExecutableSettings( 13 | linuxExecutable: 'google-chrome', 14 | macOSExecutable: 15 | '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', 16 | windowsExecutable: r'Google\Chrome\Application\chrome.exe'), 17 | Runtime.firefox: new ExecutableSettings( 18 | linuxExecutable: 'firefox', 19 | macOSExecutable: '/Applications/Firefox.app/Contents/MacOS/firefox-bin', 20 | windowsExecutable: r'Mozilla Firefox\firefox.exe'), 21 | Runtime.internetExplorer: new ExecutableSettings( 22 | windowsExecutable: r'Internet Explorer\iexplore.exe'), 23 | Runtime.phantomJS: new ExecutableSettings( 24 | linuxExecutable: 'phantomjs', 25 | macOSExecutable: 'phantomjs', 26 | windowsExecutable: 'phantomjs.exe'), 27 | Runtime.safari: new ExecutableSettings( 28 | macOSExecutable: '/Applications/Safari.app/Contents/MacOS/Safari') 29 | }); 30 | -------------------------------------------------------------------------------- /lib/src/backend/outstanding_callback_counter.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | /// A class that counts outstanding callbacks for a test and fires a future once 8 | /// they reach zero. 9 | /// 10 | /// The outstanding callback count automatically starts at 1. 11 | class OutstandingCallbackCounter { 12 | /// The number of outstanding callbacks. 13 | var _count = 1; 14 | 15 | /// A future that fires when the oustanding callback count reaches 0. 16 | Future get noOutstandingCallbacks => _completer.future; 17 | final _completer = new Completer(); 18 | 19 | /// Adds an outstanding callback. 20 | void addOutstandingCallback() { 21 | _count++; 22 | } 23 | 24 | /// Removes an outstanding callback. 25 | void removeOutstandingCallback() { 26 | _count--; 27 | if (_count != 0) return; 28 | if (_completer.isCompleted) return; 29 | _completer.complete(); 30 | } 31 | 32 | /// Removes all outstanding callbacks, forcing [noOutstandingCallbacks] to 33 | /// fire. 34 | /// 35 | /// Future calls to [addOutstandingCallback] and [removeOutstandingCallback] 36 | /// will be ignored. 37 | void removeAllOutstandingCallbacks() { 38 | if (!_completer.isCompleted) _completer.complete(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/src/runner/node/socket_channel.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @JS() 6 | library _; 7 | 8 | import 'package:js/js.dart'; 9 | import 'package:stream_channel/stream_channel.dart'; 10 | 11 | import '../../utils.dart'; 12 | 13 | @JS("require") 14 | external _Net _require(String module); 15 | 16 | @JS("process.argv") 17 | external List get _args; 18 | 19 | @JS() 20 | class _Net { 21 | external _Socket connect(int port); 22 | } 23 | 24 | @JS() 25 | class _Socket { 26 | external setEncoding(String encoding); 27 | external on(String event, void callback(String chunk)); 28 | external write(String data); 29 | } 30 | 31 | /// Returns a [StreamChannel] of JSON-encodable objects that communicates over a 32 | /// socket whose port is given by `process.argv[2]`. 33 | StreamChannel socketChannel() { 34 | var controller = new StreamChannelController( 35 | allowForeignErrors: false, sync: true); 36 | var net = _require("net"); 37 | var socket = net.connect(int.parse(_args[2])); 38 | socket.setEncoding("utf8"); 39 | 40 | controller.local.stream.listen((chunk) => socket.write(chunk)); 41 | socket.on("data", allowInterop(controller.local.sink.add)); 42 | 43 | return controller.foreign.transform(chunksToLines).transform(jsonDocument); 44 | } 45 | -------------------------------------------------------------------------------- /lib/src/util/iterable_set.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:collection'; 6 | 7 | import 'package:collection/collection.dart'; 8 | 9 | /// An unmodifiable [Set] view backed by an arbitrary [Iterable]. 10 | /// 11 | /// Note that contrary to most APIs that take iterables, this does not convert 12 | /// its argument to another collection before use. This means that if it's 13 | /// lazily-generated, that generation will happen for every operation. 14 | /// 15 | /// Note also that set operations that are usually expected to be `O(1)` or 16 | /// `O(log(n))`, such as [contains], may be `O(n)` for many underlying iterable 17 | /// types. As such, this should only be used for small iterables. 18 | class IterableSet extends SetMixin with UnmodifiableSetMixin { 19 | /// The base iterable that set operations forward to. 20 | final Iterable _base; 21 | 22 | int get length => _base.length; 23 | 24 | Iterator get iterator => _base.iterator; 25 | 26 | /// Creates a [Set] view of [base]. 27 | IterableSet(this._base); 28 | 29 | bool contains(Object element) => _base.contains(element); 30 | 31 | E lookup(Object needle) => 32 | _base.firstWhere((element) => element == needle, orElse: () => null); 33 | 34 | Set toSet() => _base.toSet(); 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/backend/message.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// A message emitted by a test. 6 | /// 7 | /// A message encompasses any textual information that should be presented to 8 | /// the user. Reporters are encouraged to visually distinguish different message 9 | /// types. 10 | class Message { 11 | final MessageType type; 12 | 13 | final String text; 14 | 15 | Message(this.type, this.text); 16 | 17 | Message.print(this.text) : type = MessageType.print; 18 | Message.skip(this.text) : type = MessageType.skip; 19 | } 20 | 21 | class MessageType { 22 | /// A message explicitly printed by the user's test. 23 | static const print = const MessageType._("print"); 24 | 25 | /// A message indicating that a test, or some portion of one, was skipped. 26 | static const skip = const MessageType._("skip"); 27 | 28 | /// The name of the message type. 29 | final String name; 30 | 31 | factory MessageType.parse(String name) { 32 | switch (name) { 33 | case "print": 34 | return MessageType.print; 35 | case "skip": 36 | return MessageType.skip; 37 | default: 38 | throw new ArgumentError('Invalid message type "$name".'); 39 | } 40 | } 41 | 42 | const MessageType._(this.name); 43 | 44 | String toString() => name; 45 | } 46 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: test 2 | version: 1.3.1-dev 3 | author: Dart Team 4 | description: A library for writing dart unit tests. 5 | homepage: https://github.com/dart-lang/test 6 | environment: 7 | sdk: '>=2.0.0-dev.54.0 <3.0.0' 8 | dependencies: 9 | analyzer: '>=0.26.4 <0.33.0' 10 | args: '>=1.4.0 <2.0.0' 11 | async: '>=1.13.0 <3.0.0' 12 | boolean_selector: '^1.0.0' 13 | collection: '^1.8.0' 14 | glob: '^1.0.0' 15 | http_multi_server: '>=1.0.0 <3.0.0' 16 | io: '^0.3.0' 17 | js: '^0.6.0' 18 | meta: '^1.1.5' 19 | multi_server_socket: '^1.0.0' 20 | node_preamble: '^1.3.0' 21 | package_resolver: '^1.0.0' 22 | path: '^1.2.0' 23 | pool: '^1.3.0' 24 | pub_semver: '^1.0.0' 25 | shelf: '>=0.6.5 <0.8.0' 26 | shelf_packages_handler: '^1.0.0' 27 | shelf_static: '^0.2.6' 28 | shelf_web_socket: '^0.2.0' 29 | source_map_stack_trace: '^1.1.4' 30 | source_maps: '^0.10.2' 31 | source_span: '^1.4.0' 32 | stack_trace: '^1.9.0' 33 | stream_channel: '^1.6.0' 34 | string_scanner: '>=0.1.1 <2.0.0' 35 | term_glyph: '^1.0.0' 36 | typed_data: '^1.0.0' 37 | vm_service_client: '^0.2.3' 38 | web_socket_channel: '^1.0.0' 39 | yaml: '^2.0.0' 40 | 41 | # Use a tight version constraint to ensure that a constraint on matcher 42 | # properly constrains all features it provides. 43 | matcher: '>=0.12.3 <0.12.4' 44 | dev_dependencies: 45 | fake_async: '>=0.1.2 <2.0.0' 46 | shelf_test_handler: '^1.0.0' 47 | test_descriptor: '^1.0.0' 48 | test_process: '^1.0.0' 49 | -------------------------------------------------------------------------------- /lib/src/runner/environment.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:async/async.dart'; 8 | 9 | /// The abstract class of environments in which test suites are 10 | /// loaded—specifically, browsers and the Dart VM. 11 | abstract class Environment { 12 | /// Whether this environment supports interactive debugging. 13 | bool get supportsDebugging; 14 | 15 | /// The URL of the Dart VM Observatory for this environment, or `null` if this 16 | /// environment doesn't run the Dart VM or the URL couldn't be detected. 17 | Uri get observatoryUrl; 18 | 19 | /// The URL of the remote debugger for this environment, or `null` if it isn't 20 | /// enabled. 21 | Uri get remoteDebuggerUrl; 22 | 23 | /// A broadcast stream that emits a `null` event whenever the user tells the 24 | /// environment to restart the current test once it's finished. 25 | /// 26 | /// Never emits an error, and never closes. 27 | Stream get onRestart; 28 | 29 | /// Displays information indicating that the test runner is paused. 30 | /// 31 | /// The returned operation will complete when the user takes action within the 32 | /// environment that should unpause the runner. If the runner is unpaused 33 | /// elsewhere, the operation should be canceled. 34 | CancelableOperation displayPause(); 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/backend/group_entry.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:stack_trace/stack_trace.dart'; 6 | 7 | import 'metadata.dart'; 8 | import 'suite_platform.dart'; 9 | import 'test.dart'; 10 | 11 | /// A [Test] or [Group]. 12 | abstract class GroupEntry { 13 | /// The name of the entry, including the prefixes from any containing 14 | /// [Group]s. 15 | /// 16 | /// This will be `null` for the root group. 17 | String get name; 18 | 19 | /// The metadata for the entry, including the metadata from any containing 20 | /// [Group]s. 21 | Metadata get metadata; 22 | 23 | /// The stack trace for the call to `test()` or `group()` that defined this 24 | /// entry, or `null` if the entry was defined in a different way. 25 | Trace get trace; 26 | 27 | /// Returns a copy of [this] with all platform-specific metadata resolved. 28 | /// 29 | /// Removes any tests and groups with [Metadata.testOn] selectors that don't 30 | /// match [platform]. Returns `null` if this entry's selector doesn't match. 31 | GroupEntry forPlatform(SuitePlatform platform); 32 | 33 | /// Returns a copy of [this] with all tests that don't match [callback] 34 | /// removed. 35 | /// 36 | /// Returns `null` if this is a test that doesn't match [callback] or a group 37 | /// where no child tests match [callback]. 38 | GroupEntry filter(bool callback(Test test)); 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014, the Dart project authors. All rights reserved. 2 | Redistribution and use in source and binary forms, with or without 3 | modification, are permitted provided that the following conditions are 4 | met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above 9 | copyright notice, this list of conditions and the following 10 | disclaimer in the documentation and/or other materials provided 11 | with the distribution. 12 | * Neither the name of Google Inc. nor the names of its 13 | contributors may be used to endorse or promote products derived 14 | from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /lib/src/runner/plugin/hack_register_platform.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:collection'; 7 | 8 | import '../../backend/runtime.dart'; 9 | import 'platform.dart'; 10 | 11 | /// The functions to use to load [_platformPlugins] in all loaders. 12 | /// 13 | /// **Do not access this outside the test package**. 14 | final platformCallbacks = 15 | new UnmodifiableMapView Function()>( 16 | _platformCallbacks); 17 | final _platformCallbacks = Function()>{}; 18 | 19 | /// **Do not call this function without express permission from the test package 20 | /// authors**. 21 | /// 22 | /// Registers a [PlatformPlugin] for [runtimes]. 23 | /// 24 | /// This globally registers a plugin for all [Loader]s. When the runner first 25 | /// requests that a suite be loaded for one of the given runtimes, this will 26 | /// call [plugin] to load the platform plugin. It may return either a 27 | /// [PlatformPlugin] or a [Future]. That plugin is then 28 | /// preserved and used to load all suites for all matching runtimes. 29 | /// 30 | /// This overwrites the default plugins for those runtimes. 31 | void registerPlatformPlugin( 32 | Iterable runtimes, FutureOr Function() plugin) { 33 | for (var runtime in runtimes) { 34 | _platformCallbacks[runtime] = plugin; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/util/exit_codes.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// Exit code constants. 6 | /// 7 | /// From [the BSD sysexits manpage][manpage]. Not every constant here is used. 8 | /// 9 | /// [manpage]: http://www.freebsd.org/cgi/man.cgi?query=sysexits 10 | /// The command completely successfully. 11 | const success = 0; 12 | 13 | /// The command was used incorrectly. 14 | const usage = 64; 15 | 16 | /// The input data was incorrect. 17 | const data = 65; 18 | 19 | /// An input file did not exist or was unreadable. 20 | const noInput = 66; 21 | 22 | /// The user specified did not exist. 23 | const noUser = 67; 24 | 25 | /// The host specified did not exist. 26 | const noHost = 68; 27 | 28 | /// A service is unavailable. 29 | const unavailable = 69; 30 | 31 | /// An internal software error has been detected. 32 | const software = 70; 33 | 34 | /// An operating system error has been detected. 35 | const os = 71; 36 | 37 | /// Some system file did not exist or was unreadable. 38 | const osFile = 72; 39 | 40 | /// A user-specified output file cannot be created. 41 | const cantCreate = 73; 42 | 43 | /// An error occurred while doing I/O on some file. 44 | const io = 74; 45 | 46 | /// Temporary failure, indicating something that is not really an error. 47 | const tempFail = 75; 48 | 49 | /// The remote system returned something invalid during a protocol exchange. 50 | const protocol = 76; 51 | 52 | /// The user did not have sufficient permissions. 53 | const noPerm = 77; 54 | 55 | /// Something was unconfigured or mis-configured. 56 | const config = 78; 57 | -------------------------------------------------------------------------------- /lib/src/runner/browser/safari.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:convert'; 7 | import 'dart:io'; 8 | 9 | import 'package:path/path.dart' as p; 10 | 11 | import '../../backend/runtime.dart'; 12 | import '../../util/io.dart'; 13 | import '../executable_settings.dart'; 14 | import 'browser.dart'; 15 | import 'default_settings.dart'; 16 | 17 | /// A class for running an instance of Safari. 18 | /// 19 | /// Any errors starting or running the process are reported through [onExit]. 20 | class Safari extends Browser { 21 | final name = "Safari"; 22 | 23 | Safari(url, {ExecutableSettings settings}) 24 | : super(() => _startBrowser(url, settings)); 25 | 26 | /// Starts a new instance of Safari open to the given [url], which may be a 27 | /// [Uri] or a [String]. 28 | static Future _startBrowser(url, ExecutableSettings settings) async { 29 | settings ??= defaultSettings[Runtime.safari]; 30 | var dir = createTempDir(); 31 | 32 | // Safari will only open files (not general URLs) via the command-line 33 | // API, so we create a dummy file to redirect it to the page we actually 34 | // want it to load. 35 | var redirect = p.join(dir, 'redirect.html'); 36 | new File(redirect).writeAsStringSync( 37 | ""); 38 | 39 | var process = await Process.start( 40 | settings.executable, settings.arguments.toList()..add(redirect)); 41 | 42 | process.exitCode 43 | .then((_) => new Directory(dir).deleteSync(recursive: true)); 44 | 45 | return process; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/src/util/one_off_handler.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'package:path/path.dart' as p; 7 | import 'package:shelf/shelf.dart' as shelf; 8 | 9 | /// A Shelf handler that provides support for one-time handlers. 10 | /// 11 | /// This is useful for handlers that only expect to be hit once before becoming 12 | /// invalid and don't need to have a persistent URL. 13 | class OneOffHandler { 14 | /// A map from URL paths to handlers. 15 | final _handlers = new Map(); 16 | 17 | /// The counter of handlers that have been activated. 18 | var _counter = 0; 19 | 20 | /// The actual [shelf.Handler] that dispatches requests. 21 | shelf.Handler get handler => _onRequest; 22 | 23 | /// Creates a new one-off handler that forwards to [handler]. 24 | /// 25 | /// Returns a string that's the URL path for hitting this handler, relative to 26 | /// the URL for the one-off handler itself. 27 | /// 28 | /// [handler] will be unmounted as soon as it receives a request. 29 | String create(shelf.Handler handler) { 30 | var path = _counter.toString(); 31 | _handlers[path] = handler; 32 | _counter++; 33 | return path; 34 | } 35 | 36 | /// Dispatches [request] to the appropriate handler. 37 | FutureOr _onRequest(shelf.Request request) { 38 | var components = p.url.split(request.url.path); 39 | if (components.isEmpty) return new shelf.Response.notFound(null); 40 | 41 | var path = components.removeAt(0); 42 | var handler = _handlers.remove(path); 43 | if (handler == null) return new shelf.Response.notFound(null); 44 | return handler(request.change(path: path)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/src/runner/browser/post_message_channel.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @JS() 6 | library test.src.runner.browser.post_message_channel; 7 | 8 | import 'dart:html'; 9 | import 'dart:js_util'; 10 | 11 | import 'package:js/js.dart'; 12 | import 'package:stream_channel/stream_channel.dart'; 13 | 14 | // Avoid using this from dart:html to work around dart-lang/sdk#32113. 15 | @JS("window.parent.postMessage") 16 | external void _postParentMessage(Object message, String targetOrigin); 17 | 18 | /// Constructs a [StreamChannel] wrapping `postMessage` communication with the 19 | /// host page. 20 | StreamChannel postMessageChannel() { 21 | var controller = new StreamChannelController(sync: true); 22 | 23 | window.onMessage.listen((message) { 24 | // A message on the Window can theoretically come from any website. It's 25 | // very unlikely that a malicious site would care about hacking someone's 26 | // unit tests, let alone be able to find the test server while it's 27 | // running, but it's good practice to check the origin anyway. 28 | if (message.origin != window.location.origin) return; 29 | message.stopPropagation(); 30 | 31 | controller.local.sink.add(message.data); 32 | }); 33 | 34 | controller.local.stream.listen((data) { 35 | // TODO(nweiz): Stop manually adding href here once issue 22554 is 36 | // fixed. 37 | _postParentMessage(jsify({"href": window.location.href, "data": data}), 38 | window.location.origin); 39 | }); 40 | 41 | // Send a ready message once we're listening so the host knows it's safe to 42 | // start sending events. 43 | _postParentMessage(jsify({"href": window.location.href, "ready": true}), 44 | window.location.origin); 45 | 46 | return controller.foreign; 47 | } 48 | -------------------------------------------------------------------------------- /lib/src/backend/suite_platform.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'operating_system.dart'; 6 | import 'runtime.dart'; 7 | 8 | /// The platform on which a test suite is loaded. 9 | class SuitePlatform { 10 | /// The runtime that hosts the suite. 11 | final Runtime runtime; 12 | 13 | /// The operating system on which the suite is running. 14 | /// 15 | /// This will always be [OperatingSystem.none] if `runtime.isBrowser` is 16 | /// true. 17 | final OperatingSystem os; 18 | 19 | /// Whether we're running on Google-internal infrastructure. 20 | final bool inGoogle; 21 | 22 | /// Creates a new platform with the given [runtime] and [os], which defaults 23 | /// to [OperatingSystem.none]. 24 | /// 25 | /// Throws an [ArgumentError] if [runtime] is a browser and [os] is not 26 | /// `null` or [OperatingSystem.none]. 27 | SuitePlatform(this.runtime, {OperatingSystem os, this.inGoogle = false}) 28 | : os = os ?? OperatingSystem.none { 29 | if (runtime.isBrowser && this.os != OperatingSystem.none) { 30 | throw new ArgumentError('No OS should be passed for runtime "$runtime".'); 31 | } 32 | } 33 | 34 | /// Converts a JSON-safe representation generated by [serialize] back into a 35 | /// [SuitePlatform]. 36 | factory SuitePlatform.deserialize(Object serialized) { 37 | var map = serialized as Map; 38 | return new SuitePlatform(new Runtime.deserialize(map['runtime']), 39 | os: OperatingSystem.find(map['os'] as String), 40 | inGoogle: map['inGoogle'] as bool); 41 | } 42 | 43 | /// Converts [this] into a JSON-safe object that can be converted back to a 44 | /// [SuitePlatform] using [new SuitePlatform.deserialize]. 45 | Object serialize() => { 46 | 'runtime': runtime.serialize(), 47 | 'os': os.identifier, 48 | 'inGoogle': inGoogle 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /lib/src/runner/browser/firefox.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:io'; 7 | 8 | import 'package:path/path.dart' as p; 9 | 10 | import '../../backend/runtime.dart'; 11 | import '../../util/io.dart'; 12 | import '../executable_settings.dart'; 13 | import 'browser.dart'; 14 | import 'default_settings.dart'; 15 | 16 | final _preferences = ''' 17 | user_pref("browser.shell.checkDefaultBrowser", false); 18 | user_pref("dom.disable_open_during_load", false); 19 | user_pref("dom.max_script_run_time", 0); 20 | '''; 21 | 22 | /// A class for running an instance of Firefox. 23 | /// 24 | /// Most of the communication with the browser is expected to happen via HTTP, 25 | /// so this exposes a bare-bones API. The browser starts as soon as the class is 26 | /// constructed, and is killed when [close] is called. 27 | /// 28 | /// Any errors starting or running the process are reported through [onExit]. 29 | class Firefox extends Browser { 30 | final name = "Firefox"; 31 | 32 | Firefox(url, {ExecutableSettings settings}) 33 | : super(() => _startBrowser(url, settings)); 34 | 35 | /// Starts a new instance of Firefox open to the given [url], which may be a 36 | /// [Uri] or a [String]. 37 | static Future _startBrowser(url, ExecutableSettings settings) async { 38 | settings ??= defaultSettings[Runtime.firefox]; 39 | var dir = createTempDir(); 40 | new File(p.join(dir, 'prefs.js')).writeAsStringSync(_preferences); 41 | 42 | var process = await Process.start( 43 | settings.executable, 44 | ["--profile", "$dir", url.toString(), "--no-remote"] 45 | ..addAll(settings.arguments), 46 | environment: {"MOZ_CRASHREPORTER_DISABLE": "1"}); 47 | 48 | process.exitCode 49 | .then((_) => new Directory(dir).deleteSync(recursive: true)); 50 | 51 | return process; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/src/runner/version.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | 7 | import 'package:yaml/yaml.dart'; 8 | 9 | /// The version number of the test runner, or `null` if it couldn't be loaded. 10 | /// 11 | /// This is a semantic version, optionally followed by a space and additional 12 | /// data about its source. 13 | final String testVersion = (() { 14 | dynamic lockfile; 15 | try { 16 | lockfile = loadYaml(new File("pubspec.lock").readAsStringSync()); 17 | } on FormatException catch (_) { 18 | return null; 19 | } on IOException catch (_) { 20 | return null; 21 | } 22 | 23 | if (lockfile is! Map) return null; 24 | var packages = lockfile["packages"]; 25 | if (packages is! Map) return null; 26 | var package = packages["test"]; 27 | if (package is! Map) return null; 28 | 29 | var source = package["source"]; 30 | if (source is! String) return null; 31 | 32 | switch (source as String) { 33 | case "hosted": 34 | var version = package["version"]; 35 | if (version is! String) return null; 36 | 37 | return version; 38 | 39 | case "git": 40 | var version = package["version"]; 41 | if (version is! String) return null; 42 | var description = package["description"]; 43 | if (description is! Map) return null; 44 | var ref = description["resolved-ref"]; 45 | if (ref is! String) return null; 46 | 47 | return "$version (${ref.substring(0, 7)})"; 48 | 49 | case "path": 50 | var version = package["version"]; 51 | if (version is! String) return null; 52 | var description = package["description"]; 53 | if (description is! Map) return null; 54 | var path = description["path"]; 55 | if (path is! String) return null; 56 | 57 | return "$version (from $path)"; 58 | 59 | default: 60 | return null; 61 | } 62 | })(); 63 | -------------------------------------------------------------------------------- /test/runner/solo_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn("vm") 6 | import 'package:test_descriptor/test_descriptor.dart' as d; 7 | 8 | import 'package:test/test.dart'; 9 | 10 | import '../io.dart'; 11 | 12 | void main() { 13 | test("only runs the tests marked as solo", () async { 14 | await d.file("test.dart", """ 15 | import 'dart:async'; 16 | 17 | import 'package:test/test.dart'; 18 | 19 | void main() { 20 | test("passes", () { 21 | expect(true, isTrue); 22 | }, solo: true); 23 | test("failed", () { 24 | throw 'some error'; 25 | }); 26 | } 27 | """).create(); 28 | 29 | var test = await runTest(["test.dart"]); 30 | expect(test.stdout, emitsThrough(contains("+1: All tests passed!"))); 31 | await test.shouldExit(0); 32 | }); 33 | 34 | test("only runs groups marked as solo", () async { 35 | await d.file("test.dart", """ 36 | import 'dart:async'; 37 | 38 | import 'package:test/test.dart'; 39 | 40 | void main() { 41 | group('solo', () { 42 | test("first pass", () { 43 | expect(true, isTrue); 44 | }); 45 | test("second pass", () { 46 | expect(true, isTrue); 47 | }); 48 | }, solo: true); 49 | group('no solo', () { 50 | test("failure", () { 51 | throw 'some error'; 52 | }); 53 | test("another failure", () { 54 | throw 'some error'; 55 | }); 56 | }); 57 | } 58 | """).create(); 59 | 60 | var test = await runTest(["test.dart"]); 61 | expect(test.stdout, emitsThrough(contains("+2: All tests passed!"))); 62 | await test.shouldExit(0); 63 | }); 64 | } 65 | -------------------------------------------------------------------------------- /lib/src/backend/suite.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'group.dart'; 6 | import 'metadata.dart'; 7 | import 'suite_platform.dart'; 8 | import 'test.dart'; 9 | 10 | /// A test suite. 11 | /// 12 | /// A test suite is a set of tests that are intended to be run together and that 13 | /// share default configuration. 14 | class Suite { 15 | /// The platform on which the suite is running. 16 | final SuitePlatform platform; 17 | 18 | /// The path to the Dart test suite, or `null` if that path is unknown. 19 | final String path; 20 | 21 | /// The metadata associated with this test suite. 22 | /// 23 | /// This is a shortcut for [group.metadata]. 24 | Metadata get metadata => group.metadata; 25 | 26 | /// The top-level group for this test suite. 27 | final Group group; 28 | 29 | /// Creates a new suite containing [entires]. 30 | /// 31 | /// If [platform] and/or [os] are passed, [group] is filtered to match that 32 | /// platform information. 33 | /// 34 | /// If [os] is passed without [platform], throws an [ArgumentError]. 35 | Suite(Group group, this.platform, {this.path}) 36 | : group = _filterGroup(group, platform); 37 | 38 | /// Returns [entries] filtered according to [platform] and [os]. 39 | /// 40 | /// Gracefully handles [platform] being null. 41 | static Group _filterGroup(Group group, SuitePlatform platform) { 42 | var filtered = group.forPlatform(platform); 43 | if (filtered != null) return filtered; 44 | return new Group.root([], metadata: group.metadata); 45 | } 46 | 47 | /// Returns a new suite with all tests matching [test] removed. 48 | /// 49 | /// Unlike [GroupEntry.filter], this never returns `null`. If all entries are 50 | /// filtered out, it returns an empty suite. 51 | Suite filter(bool callback(Test test)) { 52 | var filtered = group.filter(callback); 53 | if (filtered == null) filtered = new Group.root([], metadata: metadata); 54 | return new Suite(filtered, platform, path: path); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/src/util/path_handler.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'package:path/path.dart' as p; 7 | import 'package:shelf/shelf.dart' as shelf; 8 | 9 | /// A handler that routes to sub-handlers based on exact path prefixes. 10 | class PathHandler { 11 | /// A trie of path components to handlers. 12 | final _paths = new _Node(); 13 | 14 | /// The shelf handler. 15 | shelf.Handler get handler => _onRequest; 16 | 17 | /// Returns middleware that nests all requests beneath the URL prefix 18 | /// [beneath]. 19 | static shelf.Middleware nestedIn(String beneath) { 20 | return (handler) { 21 | var pathHandler = new PathHandler()..add(beneath, handler); 22 | return pathHandler.handler; 23 | }; 24 | } 25 | 26 | /// Routes requests at or under [path] to [handler]. 27 | /// 28 | /// If [path] is a parent or child directory of another path in this handler, 29 | /// the longest matching prefix wins. 30 | void add(String path, shelf.Handler handler) { 31 | var node = _paths; 32 | for (var component in p.url.split(path)) { 33 | node = node.children.putIfAbsent(component, () => new _Node()); 34 | } 35 | node.handler = handler; 36 | } 37 | 38 | FutureOr _onRequest(shelf.Request request) { 39 | shelf.Handler handler; 40 | int handlerIndex; 41 | var node = _paths; 42 | var components = p.url.split(request.url.path); 43 | for (var i = 0; i < components.length; i++) { 44 | node = node.children[components[i]]; 45 | if (node == null) break; 46 | if (node.handler == null) continue; 47 | handler = node.handler; 48 | handlerIndex = i; 49 | } 50 | 51 | if (handler == null) return new shelf.Response.notFound("Not found."); 52 | 53 | return handler( 54 | request.change(path: p.url.joinAll(components.take(handlerIndex + 1)))); 55 | } 56 | } 57 | 58 | /// A trie node. 59 | class _Node { 60 | shelf.Handler handler; 61 | final children = new Map(); 62 | } 63 | -------------------------------------------------------------------------------- /lib/src/frontend/never_called.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:stack_trace/stack_trace.dart'; 8 | 9 | import '../util/placeholder.dart'; 10 | import '../utils.dart'; 11 | import 'expect.dart'; 12 | import 'future_matchers.dart'; 13 | import 'utils.dart'; 14 | 15 | /// Returns a function that causes the test to fail if it's called. 16 | /// 17 | /// This can safely be passed in place of any callback that takes ten or fewer 18 | /// positional parameters. For example: 19 | /// 20 | /// ``` 21 | /// // Asserts that the stream never emits an event. 22 | /// stream.listen(neverCalled); 23 | /// ``` 24 | /// 25 | /// This also ensures that the test doesn't complete until a call to 26 | /// [pumpEventQueue] finishes, so that the callback has a chance to be called. 27 | Null Function( 28 | [Object, 29 | Object, 30 | Object, 31 | Object, 32 | Object, 33 | Object, 34 | Object, 35 | Object, 36 | Object, 37 | Object]) get neverCalled { 38 | // Make sure the test stays alive long enough to call the function if it's 39 | // going to. 40 | expect(pumpEventQueue(), completes); 41 | 42 | var zone = Zone.current; 43 | return ( 44 | [a1 = placeholder, 45 | a2 = placeholder, 46 | a3 = placeholder, 47 | a4 = placeholder, 48 | a5 = placeholder, 49 | a6 = placeholder, 50 | a7 = placeholder, 51 | a8 = placeholder, 52 | a9 = placeholder, 53 | a10 = placeholder]) { 54 | var arguments = [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10] 55 | .where((argument) => argument != placeholder) 56 | .toList(); 57 | 58 | zone.handleUncaughtError( 59 | new TestFailure( 60 | "Callback should never have been called, but it was called with" + 61 | (arguments.isEmpty 62 | ? " no arguments." 63 | : ":\n${bullet(arguments.map(prettyPrint))}")), 64 | zone.run(() => new Chain.current())); 65 | return null; 66 | }; 67 | } 68 | -------------------------------------------------------------------------------- /test/frontend/never_called_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:term_glyph/term_glyph.dart' as glyph; 8 | import 'package:test/test.dart'; 9 | 10 | import '../utils.dart'; 11 | 12 | void main() { 13 | setUpAll(() { 14 | glyph.ascii = true; 15 | }); 16 | 17 | test("doesn't throw if it isn't called", () async { 18 | var liveTest = await runTestBody(() { 19 | const Stream.empty().listen(neverCalled); 20 | }); 21 | 22 | expectTestPassed(liveTest); 23 | }); 24 | 25 | group("if it's called", () { 26 | test("throws", () async { 27 | var liveTest = await runTestBody(() { 28 | neverCalled(); 29 | }); 30 | 31 | expectTestFailed( 32 | liveTest, 33 | "Callback should never have been called, but it was called with no " 34 | "arguments."); 35 | }); 36 | 37 | test("pretty-prints arguments", () async { 38 | var liveTest = await runTestBody(() { 39 | neverCalled(1, "foo\nbar"); 40 | }); 41 | 42 | expectTestFailed( 43 | liveTest, 44 | "Callback should never have been called, but it was called with:\n" 45 | "* <1>\n" 46 | "* 'foo\\n'\n" 47 | " 'bar'"); 48 | }); 49 | 50 | test("keeps the test alive", () async { 51 | var liveTest = await runTestBody(() { 52 | pumpEventQueue(times: 10).then(neverCalled); 53 | }); 54 | 55 | expectTestFailed( 56 | liveTest, 57 | 'Callback should never have been called, but it was called with:\n' 58 | '* '); 59 | }); 60 | 61 | test("can't be caught", () async { 62 | var liveTest = await runTestBody(() { 63 | try { 64 | neverCalled(); 65 | } catch (_) { 66 | // Do nothing. 67 | } 68 | }); 69 | 70 | expectTestFailed( 71 | liveTest, 72 | 'Callback should never have been called, but it was called with ' 73 | 'no arguments.'); 74 | }); 75 | }); 76 | } 77 | -------------------------------------------------------------------------------- /lib/dart.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | // This script runs in HTML files and loads the corresponding test scripts for 6 | // a JS browser. It's used by "pub serve" and user-authored HTML files; 7 | window.onload = function() { 8 | 9 | // Sends an error message to the server indicating that the script failed to 10 | // load. 11 | // 12 | // This mimics a MultiChannel-formatted message. 13 | var sendLoadException = function(message) { 14 | window.parent.postMessage({ 15 | "href": window.location.href, 16 | "data": [0, {"type": "loadException", "message": message}] 17 | }, window.location.origin); 18 | } 19 | 20 | // Listen for dartLoadException events and forward to the server. 21 | window.addEventListener('dartLoadException', function(e) { 22 | sendLoadException(e.detail); 23 | }); 24 | 25 | // The basename of the current page. 26 | var name = window.location.href.replace(/.*\//, '').replace(/#.*/, ''); 27 | 28 | // Find . 29 | var links = document.getElementsByTagName("link"); 30 | var testLinks = []; 31 | var length = links.length; 32 | for (var i = 0; i < length; ++i) { 33 | if (links[i].rel == "x-dart-test") testLinks.push(links[i]); 34 | } 35 | 36 | if (testLinks.length != 1) { 37 | sendLoadException( 38 | 'Expected exactly 1 in ' + name + ', found ' + 39 | testLinks.length + '.'); 40 | return; 41 | } 42 | 43 | var link = testLinks[0]; 44 | 45 | if (link.href == '') { 46 | sendLoadException( 47 | 'Expected in ' + name + ' to have an "href" ' + 48 | 'attribute.'); 49 | return; 50 | } 51 | 52 | var script = document.createElement('script'); 53 | 54 | script.src = link.href + '.browser_test.dart.js'; 55 | 56 | script.onerror = function(event) { 57 | var message = "Failed to load script at " + script.src + 58 | (event.message ? ": " + event.message : "."); 59 | sendLoadException(message); 60 | } 61 | 62 | var parent = link.parentNode; 63 | document.currentScript = script; 64 | parent.replaceChild(script, link); 65 | 66 | }; 67 | -------------------------------------------------------------------------------- /lib/src/runner/configuration/reporters.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:collection'; 6 | import 'dart:io'; 7 | 8 | import '../../util/io.dart'; 9 | import '../configuration.dart'; 10 | import '../engine.dart'; 11 | import '../reporter.dart'; 12 | import '../reporter/compact.dart'; 13 | import '../reporter/expanded.dart'; 14 | import '../reporter/json.dart'; 15 | 16 | /// Constructs a reporter for the provided engine with the provided 17 | /// configuration. 18 | typedef Reporter ReporterFactory(Configuration configuration, Engine engine); 19 | 20 | /// Container for a reporter description and corresponding factory. 21 | class ReporterDetails { 22 | final String description; 23 | final ReporterFactory factory; 24 | ReporterDetails(this.description, this.factory); 25 | } 26 | 27 | /// All reporters and their corresponding details. 28 | final UnmodifiableMapView allReporters = 29 | new UnmodifiableMapView(_allReporters); 30 | 31 | final _allReporters = { 32 | "expanded": new ReporterDetails( 33 | "A separate line for each update.", 34 | (config, engine) => ExpandedReporter.watch(engine, 35 | color: config.color, 36 | printPath: config.paths.length > 1 || 37 | new Directory(config.paths.single).existsSync(), 38 | printPlatform: config.suiteDefaults.runtimes.length > 1)), 39 | "compact": new ReporterDetails("A single line, updated continuously.", 40 | (_, engine) => CompactReporter.watch(engine)), 41 | "json": new ReporterDetails( 42 | "A machine-readable format (see https://goo.gl/gBsV1a).", 43 | (_, engine) => JsonReporter.watch(engine)), 44 | }; 45 | 46 | final defaultReporter = 47 | inTestTests ? 'expanded' : canUseSpecialChars ? 'compact' : 'expanded'; 48 | 49 | /// **Do not call this function without express permission from the test package 50 | /// authors**. 51 | /// 52 | /// This globally registers a reporter. 53 | void registerReporter(String name, ReporterDetails reporterDetails) { 54 | _allReporters[name] = reporterDetails; 55 | } 56 | -------------------------------------------------------------------------------- /lib/src/runner/browser/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | test Browser Host 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /lib/src/frontend/async_matcher.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:matcher/matcher.dart'; 8 | 9 | import '../backend/invoker.dart'; 10 | import 'expect.dart'; 11 | 12 | /// A matcher that does asynchronous computation. 13 | /// 14 | /// Rather than implementing [matches], subclasses implement [matchAsync]. 15 | /// [AsyncMatcher.matches] ensures that the test doesn't complete until the 16 | /// returned future completes, and [expect] returns a future that completes when 17 | /// the returned future completes so that tests can wait for it. 18 | abstract class AsyncMatcher extends Matcher { 19 | const AsyncMatcher(); 20 | 21 | /// Returns `null` if this matches [item], or a [String] description of the 22 | /// failure if it doesn't match. 23 | /// 24 | /// This can return a [Future] or a synchronous value. If it returns a 25 | /// [Future], neither [expect] nor the test will complete until that [Future] 26 | /// completes. 27 | /// 28 | /// If this returns a [String] synchronously, [expect] will synchronously 29 | /// throw a [TestFailure] and [matches] will synchronusly return `false`. 30 | /*FutureOr*/ matchAsync(item); 31 | 32 | bool matches(item, Map matchState) { 33 | var result = matchAsync(item); 34 | expect( 35 | result, 36 | anyOf([ 37 | equals(null), 38 | new TypeMatcher(), 39 | new TypeMatcher() 40 | ]), 41 | reason: "matchAsync() may only return a String, a Future, or null."); 42 | 43 | if (result is Future) { 44 | Invoker.current.addOutstandingCallback(); 45 | result.then((realResult) { 46 | if (realResult != null) { 47 | // ignore: deprecated_member_use 48 | fail(formatFailure(this, item, realResult as String)); 49 | } 50 | Invoker.current.removeOutstandingCallback(); 51 | }); 52 | } else if (result is String) { 53 | matchState[this] = result; 54 | return false; 55 | } 56 | 57 | return true; 58 | } 59 | 60 | Description describeMismatch( 61 | item, Description description, Map matchState, bool verbose) => 62 | new StringDescription(matchState[this] as String); 63 | } 64 | -------------------------------------------------------------------------------- /lib/src/frontend/throws_matchers.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:matcher/matcher.dart'; 6 | 7 | import 'throws_matcher.dart'; 8 | 9 | /// A matcher for functions that throw ArgumentError. 10 | const Matcher throwsArgumentError = 11 | // ignore: deprecated_member_use 12 | const Throws(isArgumentError); 13 | 14 | /// A matcher for functions that throw ConcurrentModificationError. 15 | const Matcher throwsConcurrentModificationError = 16 | // ignore: deprecated_member_use 17 | const Throws(isConcurrentModificationError); 18 | 19 | /// A matcher for functions that throw CyclicInitializationError. 20 | const Matcher throwsCyclicInitializationError = 21 | // ignore: deprecated_member_use 22 | const Throws(isCyclicInitializationError); 23 | 24 | /// A matcher for functions that throw Exception. 25 | const Matcher throwsException = 26 | // ignore: deprecated_member_use 27 | const Throws(isException); 28 | 29 | /// A matcher for functions that throw FormatException. 30 | const Matcher throwsFormatException = 31 | // ignore: deprecated_member_use 32 | const Throws(isFormatException); 33 | 34 | /// A matcher for functions that throw NoSuchMethodError. 35 | const Matcher throwsNoSuchMethodError = 36 | // ignore: deprecated_member_use 37 | const Throws(isNoSuchMethodError); 38 | 39 | /// A matcher for functions that throw NullThrownError. 40 | const Matcher throwsNullThrownError = 41 | // ignore: deprecated_member_use 42 | const Throws(isNullThrownError); 43 | 44 | /// A matcher for functions that throw RangeError. 45 | const Matcher throwsRangeError = 46 | // ignore: deprecated_member_use 47 | const Throws(isRangeError); 48 | 49 | /// A matcher for functions that throw StateError. 50 | const Matcher throwsStateError = 51 | // ignore: deprecated_member_use 52 | const Throws(isStateError); 53 | 54 | /// A matcher for functions that throw Exception. 55 | const Matcher throwsUnimplementedError = 56 | // ignore: deprecated_member_use 57 | const Throws(isUnimplementedError); 58 | 59 | /// A matcher for functions that throw UnsupportedError. 60 | const Matcher throwsUnsupportedError = 61 | // ignore: deprecated_member_use 62 | const Throws(isUnsupportedError); 63 | -------------------------------------------------------------------------------- /lib/src/runner/spawn_hybrid.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:isolate'; 7 | 8 | import 'package:async/async.dart'; 9 | import 'package:stream_channel/stream_channel.dart'; 10 | 11 | import '../util/dart.dart' as dart; 12 | import "../util/remote_exception.dart"; 13 | 14 | /// Spawns a hybrid isolate from [url] with the given [message], and returns a 15 | /// [StreamChannel] that communicates with it. 16 | /// 17 | /// This connects the main isolate to the hybrid isolate, whereas 18 | /// `lib/src/frontend/spawn_hybrid.dart` connects the test isolate to the main 19 | /// isolate. 20 | StreamChannel spawnHybridUri(String url, Object message) { 21 | return StreamChannelCompleter.fromFuture(() async { 22 | var port = new ReceivePort(); 23 | var onExitPort = new ReceivePort(); 24 | try { 25 | var code = """ 26 | import "package:test/src/runner/hybrid_listener.dart"; 27 | 28 | import "${url.replaceAll(r'$', '%24')}" as lib; 29 | 30 | void main(_, List data) => listen(() => lib.hybridMain, data); 31 | """; 32 | 33 | var isolate = await dart.runInIsolate(code, [port.sendPort, message], 34 | onExit: onExitPort.sendPort); 35 | 36 | // Ensure that we close [port] and [channel] when the isolate exits. 37 | var disconnector = new Disconnector(); 38 | onExitPort.listen((_) { 39 | disconnector.disconnect(); 40 | onExitPort.close(); 41 | }); 42 | 43 | return new IsolateChannel.connectReceive(port) 44 | .transform(disconnector) 45 | .transformSink( 46 | new StreamSinkTransformer.fromHandlers(handleDone: (sink) { 47 | // If the user closes the stream channel, kill the isolate. 48 | isolate.kill(); 49 | onExitPort.close(); 50 | sink.close(); 51 | })); 52 | } catch (error, stackTrace) { 53 | port.close(); 54 | onExitPort.close(); 55 | 56 | // Make sure any errors in spawning the isolate are forwarded to the test. 57 | return new StreamChannel( 58 | new Stream.fromFuture(new Future.value({ 59 | "type": "error", 60 | "error": RemoteException.serialize(error, stackTrace) 61 | })), 62 | new NullStreamSink()); 63 | } 64 | }()); 65 | } 66 | -------------------------------------------------------------------------------- /lib/src/runner/load_exception.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:isolate'; 6 | 7 | import 'package:path/path.dart' as p; 8 | import 'package:source_span/source_span.dart'; 9 | 10 | import '../utils.dart'; 11 | 12 | /// A regular expression for matching filename annotations in 13 | /// [IsolateSpawnException] messages. 14 | final _isolateFileRegExp = 15 | new RegExp(r"^'(file:/[^']+)': (error|warning): ", multiLine: true); 16 | 17 | class LoadException implements Exception { 18 | final String path; 19 | 20 | final Object innerError; 21 | 22 | LoadException(this.path, this.innerError); 23 | 24 | String toString({bool color = false}) { 25 | var buffer = new StringBuffer(); 26 | if (color) buffer.write('\u001b[31m'); // red 27 | buffer.write('Failed to load "$path":'); 28 | if (color) buffer.write('\u001b[0m'); // no color 29 | 30 | var innerString = getErrorMessage(innerError); 31 | if (innerError is IsolateSpawnException) { 32 | // If this is a parse error, clean up the noisy filename annotations. 33 | innerString = innerString.replaceAllMapped(_isolateFileRegExp, (match) { 34 | if (p.fromUri(match[1]) == p.absolute(path)) return ""; 35 | return "${p.prettyUri(match[1])}: "; 36 | }); 37 | 38 | // If this is a file system error, get rid of both the preamble and the 39 | // useless stack trace. 40 | 41 | // This message was used prior to 1.11.0-dev.3.0. 42 | innerString = innerString.replaceFirst( 43 | "Unhandled exception:\n" 44 | "Uncaught Error: Load Error: ", 45 | ""); 46 | 47 | // This message was used after 1.11.0-dev.3.0. 48 | innerString = innerString.replaceFirst( 49 | "Unhandled exception:\n" 50 | "Load Error for ", 51 | ""); 52 | 53 | innerString = innerString.replaceFirst("FileSystemException: ", ""); 54 | innerString = innerString.split("Stack Trace:\n").first.trim(); 55 | } 56 | if (innerError is SourceSpanException) { 57 | innerString = (innerError as SourceSpanException) 58 | .toString(color: color) 59 | .replaceFirst(" of $path", ""); 60 | } 61 | 62 | buffer.write(innerString.contains("\n") ? "\n" : " "); 63 | buffer.write(innerString); 64 | return buffer.toString(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/src/frontend/prints_matcher.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:matcher/matcher.dart'; 8 | 9 | import '../utils.dart'; 10 | import 'async_matcher.dart'; 11 | import 'expect.dart'; 12 | 13 | /// Matches a [Function] that prints text that matches [matcher]. 14 | /// 15 | /// [matcher] may be a String or a [Matcher]. 16 | /// 17 | /// If the function this runs against returns a [Future], all text printed by 18 | /// the function (using [Zone] scoping) until that Future completes is matched. 19 | /// 20 | /// This only tracks text printed using the [print] function. 21 | /// 22 | /// This returns an [AsyncMatcher], so [expect] won't complete until the matched 23 | /// function does. 24 | Matcher prints(matcher) => new _Prints(wrapMatcher(matcher)); 25 | 26 | class _Prints extends AsyncMatcher { 27 | final Matcher _matcher; 28 | 29 | _Prints(this._matcher); 30 | 31 | // Avoid async/await so we synchronously fail if the function is 32 | // synchronous. 33 | /*FutureOr*/ matchAsync(item) { 34 | if (item is! Function()) return "was not a unary Function"; 35 | 36 | var buffer = new StringBuffer(); 37 | var result = runZoned(item as Function(), 38 | zoneSpecification: new ZoneSpecification(print: (_, __, ____, line) { 39 | buffer.writeln(line); 40 | })); 41 | 42 | return result is Future 43 | ? result.then((_) => _check(buffer.toString())) 44 | : _check(buffer.toString()); 45 | } 46 | 47 | Description describe(Description description) => 48 | description.add('prints ').addDescriptionOf(_matcher); 49 | 50 | /// Verifies that [actual] matches [_matcher] and returns a [String] 51 | /// description of the failure if it doesn't. 52 | String _check(String actual) { 53 | var matchState = {}; 54 | if (_matcher.matches(actual, matchState)) return null; 55 | 56 | var result = _matcher 57 | .describeMismatch(actual, new StringDescription(), matchState, false) 58 | .toString(); 59 | 60 | var buffer = new StringBuffer(); 61 | if (actual.isEmpty) { 62 | buffer.writeln('printed nothing'); 63 | } else { 64 | buffer.writeln(indent(prettyPrint(actual), first: 'printed ')); 65 | } 66 | if (result.isNotEmpty) buffer.writeln(indent(result, first: ' which ')); 67 | return buffer.toString().trimRight(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/src/runner/suite_channel_manager.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:stream_channel/stream_channel.dart'; 8 | 9 | /// The key used to look up [SuiteChannelManager.current] in a zone. 10 | final _currentKey = new Object(); 11 | 12 | /// A class that connects incoming and outgoing channels with the same names. 13 | class SuiteChannelManager { 14 | /// Connections from the test runner that have yet to connect to corresponding 15 | /// calls to [suiteChannel] within this worker. 16 | final _incomingConnections = {}; 17 | 18 | /// Connections from calls to [suiteChannel] that have yet to connect to 19 | /// corresponding connections from the test runner. 20 | final _outgoingConnections = {}; 21 | 22 | /// The channel names that have already been used. 23 | final _names = new Set(); 24 | 25 | /// Returns the current manager, or `null` if this isn't called within a call 26 | /// to [asCurrent]. 27 | static SuiteChannelManager get current => 28 | Zone.current[_currentKey] as SuiteChannelManager; 29 | 30 | /// Runs [body] with [this] as [SuiteChannelManager.current]. 31 | /// 32 | /// This is zone-scoped, so [this] will be the current configuration in any 33 | /// asynchronous callbacks transitively created by [body]. 34 | T asCurrent(T body()) => runZoned(body, zoneValues: {_currentKey: this}); 35 | 36 | /// Creates a connection to the test runnner's channel with the given [name]. 37 | StreamChannel connectOut(String name) { 38 | if (_incomingConnections.containsKey(name)) { 39 | return _incomingConnections[name]; 40 | } else if (_names.contains(name)) { 41 | throw new StateError('Duplicate suiteChannel() connection "$name".'); 42 | } else { 43 | _names.add(name); 44 | var completer = new StreamChannelCompleter(); 45 | _outgoingConnections[name] = completer; 46 | return completer.channel; 47 | } 48 | } 49 | 50 | /// Connects [channel] to this worker's channel with the given [name]. 51 | void connectIn(String name, StreamChannel channel) { 52 | if (_outgoingConnections.containsKey(name)) { 53 | _outgoingConnections.remove(name).setChannel(channel); 54 | } else if (_incomingConnections.containsKey(name)) { 55 | throw new StateError( 56 | 'Duplicate RunnerSuite.channel() connection "$name".'); 57 | } else { 58 | _incomingConnections[name] = channel; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/frontend/test_chain_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn("vm") 6 | 7 | import 'dart:convert'; 8 | 9 | import 'package:test_descriptor/test_descriptor.dart' as d; 10 | import 'package:test/test.dart'; 11 | 12 | import '../io.dart'; 13 | 14 | void main() { 15 | setUp(() async { 16 | await d.file("test.dart", """ 17 | import 'dart:async'; 18 | 19 | import 'package:test/test.dart'; 20 | 21 | void main() { 22 | test("failure", () async{ 23 | await new Future((){}); 24 | await new Future((){}); 25 | throw "oh no"; 26 | }); 27 | } 28 | """).create(); 29 | }); 30 | test("folds packages contained in the except list", () async { 31 | await d 32 | .file( 33 | "dart_test.yaml", 34 | jsonEncode({ 35 | "fold_stack_frames": { 36 | "except": ["stream_channel"] 37 | } 38 | })) 39 | .create(); 40 | var test = await runTest(["test.dart"]); 41 | expect(test.stdoutStream(), neverEmits(contains('package:stream_channel'))); 42 | await test.shouldExit(1); 43 | }); 44 | 45 | test("by default folds both stream_channel and test packages", () async { 46 | var test = await runTest(["test.dart"]); 47 | expect(test.stdoutStream(), neverEmits(contains('package:test'))); 48 | expect(test.stdoutStream(), neverEmits(contains('package:stream_channel'))); 49 | await test.shouldExit(1); 50 | }); 51 | 52 | test("folds all packages not contained in the only list", () async { 53 | await d 54 | .file( 55 | "dart_test.yaml", 56 | jsonEncode({ 57 | "fold_stack_frames": { 58 | "only": ["test"] 59 | } 60 | })) 61 | .create(); 62 | var test = await runTest(["test.dart"]); 63 | expect(test.stdoutStream(), neverEmits(contains('package:stream_channel'))); 64 | await test.shouldExit(1); 65 | }); 66 | 67 | test("does not fold packages in the only list", () async { 68 | await d 69 | .file( 70 | "dart_test.yaml", 71 | jsonEncode({ 72 | "fold_stack_frames": { 73 | "only": ["test"] 74 | } 75 | })) 76 | .create(); 77 | var test = await runTest(["test.dart"]); 78 | expect(test.stdoutStream(), emitsThrough(contains('package:test'))); 79 | await test.shouldExit(1); 80 | }); 81 | } 82 | -------------------------------------------------------------------------------- /test/runner/browser/safari_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn("vm") 6 | @Tags(const ["safari"]) 7 | 8 | import 'package:test_descriptor/test_descriptor.dart' as d; 9 | 10 | import 'package:test/src/runner/browser/safari.dart'; 11 | import 'package:test/src/runner/executable_settings.dart'; 12 | import 'package:test/test.dart'; 13 | 14 | import '../../io.dart'; 15 | import '../../utils.dart'; 16 | import 'code_server.dart'; 17 | 18 | void main() { 19 | test("starts Safari with the given URL", () async { 20 | var server = await CodeServer.start(); 21 | 22 | server.handleJavaScript(''' 23 | var webSocket = new WebSocket(window.location.href.replace("http://", "ws://")); 24 | webSocket.addEventListener("open", function() { 25 | webSocket.send("loaded!"); 26 | }); 27 | '''); 28 | var webSocket = server.handleWebSocket(); 29 | 30 | var safari = new Safari(server.url); 31 | addTearDown(() => safari.close()); 32 | 33 | expect(await (await webSocket).stream.first, equals("loaded!")); 34 | }); 35 | 36 | test("a process can be killed synchronously after it's started", () async { 37 | var server = await CodeServer.start(); 38 | 39 | var safari = new Safari(server.url); 40 | await safari.close(); 41 | }); 42 | 43 | test("reports an error in onExit", () { 44 | var safari = new Safari("http://dart-lang.org", 45 | settings: new ExecutableSettings( 46 | linuxExecutable: "_does_not_exist", 47 | macOSExecutable: "_does_not_exist", 48 | windowsExecutable: "_does_not_exist")); 49 | expect( 50 | safari.onExit, 51 | throwsA(isApplicationException( 52 | startsWith("Failed to run Safari: $noSuchFileMessage")))); 53 | }); 54 | 55 | test("can run successful tests", () async { 56 | await d.file("test.dart", """ 57 | import 'package:test/test.dart'; 58 | 59 | void main() { 60 | test("success", () {}); 61 | } 62 | """).create(); 63 | 64 | var test = await runTest(["-p", "safari", "test.dart"]); 65 | expect(test.stdout, emitsThrough(contains("+1: All tests passed!"))); 66 | await test.shouldExit(0); 67 | }); 68 | 69 | test("can run failing tests", () async { 70 | await d.file("test.dart", """ 71 | import 'package:test/test.dart'; 72 | 73 | void main() { 74 | test("failure", () => throw new TestFailure("oh no")); 75 | } 76 | """).create(); 77 | 78 | var test = await runTest(["-p", "safari", "test.dart"]); 79 | expect(test.stdout, emitsThrough(contains("-1: Some tests failed."))); 80 | await test.shouldExit(1); 81 | }); 82 | } 83 | -------------------------------------------------------------------------------- /test/runner/browser/firefox_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn("vm") 6 | @Tags(const ["firefox"]) 7 | 8 | import 'package:test_descriptor/test_descriptor.dart' as d; 9 | 10 | import 'package:test/src/runner/browser/firefox.dart'; 11 | import 'package:test/src/runner/executable_settings.dart'; 12 | import 'package:test/test.dart'; 13 | 14 | import '../../io.dart'; 15 | import '../../utils.dart'; 16 | import 'code_server.dart'; 17 | 18 | void main() { 19 | test("starts Firefox with the given URL", () async { 20 | var server = await CodeServer.start(); 21 | 22 | server.handleJavaScript(''' 23 | var webSocket = new WebSocket(window.location.href.replace("http://", "ws://")); 24 | webSocket.addEventListener("open", function() { 25 | webSocket.send("loaded!"); 26 | }); 27 | '''); 28 | var webSocket = server.handleWebSocket(); 29 | 30 | var firefox = new Firefox(server.url); 31 | addTearDown(() => firefox.close()); 32 | 33 | expect(await (await webSocket).stream.first, equals("loaded!")); 34 | }); 35 | 36 | test("a process can be killed synchronously after it's started", () async { 37 | var server = await CodeServer.start(); 38 | 39 | var firefox = new Firefox(server.url); 40 | await firefox.close(); 41 | }); 42 | 43 | test("reports an error in onExit", () { 44 | var firefox = new Firefox("http://dart-lang.org", 45 | settings: new ExecutableSettings( 46 | linuxExecutable: "_does_not_exist", 47 | macOSExecutable: "_does_not_exist", 48 | windowsExecutable: "_does_not_exist")); 49 | expect( 50 | firefox.onExit, 51 | throwsA(isApplicationException( 52 | startsWith("Failed to run Firefox: $noSuchFileMessage")))); 53 | }); 54 | 55 | test("can run successful tests", () async { 56 | await d.file("test.dart", """ 57 | import 'package:test/test.dart'; 58 | 59 | void main() { 60 | test("success", () {}); 61 | } 62 | """).create(); 63 | 64 | var test = await runTest(["-p", "firefox", "test.dart"]); 65 | expect(test.stdout, emitsThrough(contains("+1: All tests passed!"))); 66 | await test.shouldExit(0); 67 | }); 68 | 69 | test("can run failing tests", () async { 70 | await d.file("test.dart", """ 71 | import 'package:test/test.dart'; 72 | 73 | void main() { 74 | test("failure", () => throw new TestFailure("oh no")); 75 | } 76 | """).create(); 77 | 78 | var test = await runTest(["-p", "firefox", "test.dart"]); 79 | expect(test.stdout, emitsThrough(contains("-1: Some tests failed."))); 80 | await test.shouldExit(1); 81 | }); 82 | } 83 | -------------------------------------------------------------------------------- /test/runner/browser/internet_explorer_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn("vm") 6 | @Tags(const ["ie"]) 7 | 8 | import 'package:test_descriptor/test_descriptor.dart' as d; 9 | 10 | import 'package:test/src/runner/browser/internet_explorer.dart'; 11 | import 'package:test/src/runner/executable_settings.dart'; 12 | import 'package:test/test.dart'; 13 | 14 | import '../../io.dart'; 15 | import '../../utils.dart'; 16 | import 'code_server.dart'; 17 | 18 | void main() { 19 | test("starts IE with the given URL", () async { 20 | var server = await CodeServer.start(); 21 | 22 | server.handleJavaScript(''' 23 | var webSocket = new WebSocket(window.location.href.replace("http://", "ws://")); 24 | webSocket.addEventListener("open", function() { 25 | webSocket.send("loaded!"); 26 | }); 27 | '''); 28 | var webSocket = server.handleWebSocket(); 29 | 30 | var ie = new InternetExplorer(server.url); 31 | addTearDown(() => ie.close()); 32 | 33 | expect(await (await webSocket).stream.first, equals("loaded!")); 34 | }); 35 | 36 | test("a process can be killed synchronously after it's started", () async { 37 | var server = await CodeServer.start(); 38 | 39 | var ie = new InternetExplorer(server.url); 40 | await ie.close(); 41 | }); 42 | 43 | test("reports an error in onExit", () { 44 | var ie = new InternetExplorer("http://dart-lang.org", 45 | settings: new ExecutableSettings( 46 | linuxExecutable: "_does_not_exist", 47 | macOSExecutable: "_does_not_exist", 48 | windowsExecutable: "_does_not_exist")); 49 | expect( 50 | ie.onExit, 51 | throwsA(isApplicationException(startsWith( 52 | "Failed to run Internet Explorer: $noSuchFileMessage")))); 53 | }); 54 | 55 | test("can run successful tests", () async { 56 | await d.file("test.dart", """ 57 | import 'package:test/test.dart'; 58 | 59 | void main() { 60 | test("success", () {}); 61 | } 62 | """).create(); 63 | 64 | var test = await runTest(["-p", "ie", "test.dart"]); 65 | expect(test.stdout, emitsThrough(contains("+1: All tests passed!"))); 66 | await test.shouldExit(0); 67 | }); 68 | 69 | test("can run failing tests", () async { 70 | await d.file("test.dart", """ 71 | import 'package:test/test.dart'; 72 | 73 | void main() { 74 | test("failure", () => throw new TestFailure("oh no")); 75 | } 76 | """).create(); 77 | 78 | var test = await runTest(["-p", "ie", "test.dart"]); 79 | expect(test.stdout, emitsThrough(contains("-1: Some tests failed."))); 80 | await test.shouldExit(1); 81 | }); 82 | } 83 | -------------------------------------------------------------------------------- /test/runner/browser/phantom_js_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn("vm") 6 | @Tags(const ["phantomjs"]) 7 | 8 | import 'package:test_descriptor/test_descriptor.dart' as d; 9 | 10 | import 'package:test/src/runner/browser/phantom_js.dart'; 11 | import 'package:test/src/runner/executable_settings.dart'; 12 | import 'package:test/test.dart'; 13 | 14 | import '../../io.dart'; 15 | import '../../utils.dart'; 16 | import 'code_server.dart'; 17 | 18 | void main() { 19 | test("starts PhantomJS with the given URL", () async { 20 | var server = await CodeServer.start(); 21 | 22 | server.handleJavaScript(''' 23 | var webSocket = new WebSocket(window.location.href.replace("http://", "ws://")); 24 | webSocket.addEventListener("open", function() { 25 | webSocket.send("loaded!"); 26 | }); 27 | '''); 28 | var webSocket = server.handleWebSocket(); 29 | 30 | var phantomJS = new PhantomJS(server.url); 31 | addTearDown(() => phantomJS.close()); 32 | 33 | expect(await (await webSocket).stream.first, equals("loaded!")); 34 | }); 35 | 36 | test("a process can be killed synchronously after it's started", () async { 37 | var server = await CodeServer.start(); 38 | 39 | var phantomJS = new PhantomJS(server.url); 40 | await phantomJS.close(); 41 | }); 42 | 43 | test("reports an error in onExit", () { 44 | var phantomJS = new PhantomJS("http://dart-lang.org", 45 | settings: new ExecutableSettings( 46 | linuxExecutable: "_does_not_exist", 47 | macOSExecutable: "_does_not_exist", 48 | windowsExecutable: "_does_not_exist")); 49 | expect( 50 | phantomJS.onExit, 51 | throwsA(isApplicationException( 52 | startsWith("Failed to run PhantomJS: $noSuchFileMessage")))); 53 | }); 54 | 55 | test("can run successful tests", () async { 56 | await d.file("test.dart", """ 57 | import 'package:test/test.dart'; 58 | 59 | void main() { 60 | test("success", () {}); 61 | } 62 | """).create(); 63 | 64 | var test = await runTest(["-p", "phantomjs", "test.dart"]); 65 | expect(test.stdout, emitsThrough(contains("+1: All tests passed!"))); 66 | await test.shouldExit(0); 67 | }); 68 | 69 | test("can run failing tests", () async { 70 | await d.file("test.dart", """ 71 | import 'package:test/test.dart'; 72 | 73 | void main() { 74 | test("failure", () => throw new TestFailure("oh no")); 75 | } 76 | """).create(); 77 | 78 | var test = await runTest(["-p", "phantomjs", "test.dart"]); 79 | expect(test.stdout, emitsThrough(contains("-1: Some tests failed."))); 80 | await test.shouldExit(1); 81 | }); 82 | } 83 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at 2 | the end). 3 | 4 | ### Before you contribute 5 | 6 | Before we can use your code, you must sign the 7 | [Google Individual Contributor License Agreement][CLA] (CLA), which you can do 8 | online. The CLA is necessary mainly because you own the copyright to your 9 | changes, even after your contribution becomes part of our codebase, so we need 10 | your permission to use and distribute your code. We also need to be sure of 11 | various other things—for instance that you'll tell us if you know that your code 12 | infringes on other people's patents. You don't have to sign the CLA until after 13 | you've submitted your code for review and a member has approved it, but you must 14 | do it before we can put your code into our codebase. 15 | 16 | Before you start working on a larger contribution, you should get in touch with 17 | us first through the issue tracker with your idea so that we can help out and 18 | possibly guide you. Coordinating up front makes it much easier to avoid 19 | frustration later on. 20 | 21 | [CLA]: https://cla.developers.google.com/about/google-individual 22 | 23 | ### Code reviews 24 | 25 | All submissions, including submissions by project members, require review. We 26 | recommend [forking the repository][fork], making changes in your fork, and 27 | [sending us a pull request][pr] so we can review the changes and merge them into 28 | this repository. Note that this package doesn't use the Dart formatter. The 29 | reviewer will reformat your code themselves if necessary. 30 | 31 | [fork]: https://help.github.com/articles/about-forks/ 32 | [pr]: https://help.github.com/articles/creating-a-pull-request/ 33 | 34 | Functional changes will require tests to be added or changed. The tests live in 35 | the `test/` directory, and are run with `pub run test`. If you need to create 36 | new tests, use the existing tests as a guideline for what they should look like. 37 | 38 | Before you send your pull request, make sure all the tests pass! To run all the 39 | tests, you'll need: 40 | 41 | * [PhantomJS][] version 2.0.0 or higher. 42 | 43 | [PhantomJS]: http://phantomjs.org/ 44 | 45 | Once you have these, just run `pub run test`. 46 | 47 | ### File headers 48 | 49 | All files in the project must start with the following header. 50 | 51 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 52 | // for details. All rights reserved. Use of this source code is governed by a 53 | // BSD-style license that can be found in the LICENSE file. 54 | 55 | ### The small print 56 | 57 | Contributions made by corporations are covered by a different agreement than the 58 | one above, the 59 | [Software Grant and Corporate Contributor License Agreement][CCLA]. 60 | 61 | [CCLA]: https://developers.google.com/open-source/cla/corporate 62 | -------------------------------------------------------------------------------- /lib/src/runner/plugin/customizable_platform.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:yaml/yaml.dart'; 6 | 7 | import '../../backend/runtime.dart'; 8 | import 'platform.dart'; 9 | 10 | /// An interface for [PlatformPlugin]s that support per-platform customization. 11 | /// 12 | /// If a [PlatformPlugin] implements this, the user will be able to override the 13 | /// [Runtime]s it supports using the 14 | /// [`override_platforms`][override_platforms] configuration field, and define 15 | /// new runtimes based on them using the [`define_platforms`][define_platforms] 16 | /// field. The custom settings will be passed to the plugin using 17 | /// [customizePlatform]. 18 | /// 19 | /// [override_platforms]: https://github.com/dart-lang/test/blob/master/doc/configuration.md#override_platforms 20 | /// [define_platforms]: https://github.com/dart-lang/test/blob/master/doc/configuration.md#define_platforms 21 | /// 22 | /// Plugins that implement this **must** support children of recognized runtimes 23 | /// (created by [Runtime.extend]) in their [loadChannel] or [load] methods. 24 | abstract class CustomizablePlatform extends PlatformPlugin { 25 | /// Parses user-provided [settings] for a custom platform into a 26 | /// plugin-defined format. 27 | /// 28 | /// The [settings] come from a user's configuration file. The parsed output 29 | /// will be passed to [customizePlatform]. 30 | /// 31 | /// Subclasses should throw [SourceSpanFormatException]s if [settings] 32 | /// contains invalid configuration. Unrecognized fields should be ignored if 33 | /// possible. 34 | T parsePlatformSettings(YamlMap settings); 35 | 36 | /// Merges [settings1] with [settings2] and returns a new settings object that 37 | /// includes the configuration of both. 38 | /// 39 | /// When the settings conflict, [settings2] should take priority. 40 | /// 41 | /// This is used to merge global settings with local settings, or a custom 42 | /// platform's settings with its parent's. 43 | T mergePlatformSettings(T settings1, T settings2); 44 | 45 | /// Defines user-provided [settings] for [runtime]. 46 | /// 47 | /// The [runtime] is a runtime this plugin was declared to accept when 48 | /// registered with [Loader.registerPlatformPlugin], or a runtime whose 49 | /// [Runtime.parent] is one of those runtimes. Subclasses should customize the 50 | /// behavior for these runtimes when [loadChannel] or [load] is called with 51 | /// the given [runtime], using the [settings] which are parsed by 52 | /// [parsePlatformSettings]. This is guaranteed to be called before either 53 | /// `load` method. 54 | void customizePlatform(Runtime runtime, T settings); 55 | } 56 | -------------------------------------------------------------------------------- /test/runner/browser/chrome_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn("vm") 6 | @Tags(const ["chrome"]) 7 | 8 | import 'package:test_descriptor/test_descriptor.dart' as d; 9 | 10 | import 'package:test/src/runner/executable_settings.dart'; 11 | import 'package:test/src/runner/browser/chrome.dart'; 12 | import 'package:test/test.dart'; 13 | 14 | import '../../io.dart'; 15 | import '../../utils.dart'; 16 | import 'code_server.dart'; 17 | 18 | void main() { 19 | test("starts Chrome with the given URL", () async { 20 | var server = await CodeServer.start(); 21 | 22 | server.handleJavaScript(''' 23 | var webSocket = new WebSocket(window.location.href.replace("http://", "ws://")); 24 | webSocket.addEventListener("open", function() { 25 | webSocket.send("loaded!"); 26 | }); 27 | '''); 28 | var webSocket = server.handleWebSocket(); 29 | 30 | var chrome = new Chrome(server.url); 31 | addTearDown(() => chrome.close()); 32 | 33 | expect(await (await webSocket).stream.first, equals("loaded!")); 34 | }, 35 | // It's not clear why, but this test in particular seems to time out 36 | // when run in parallel with many other tests. 37 | timeout: new Timeout.factor(2)); 38 | 39 | test("a process can be killed synchronously after it's started", () async { 40 | var server = await CodeServer.start(); 41 | var chrome = new Chrome(server.url); 42 | await chrome.close(); 43 | }); 44 | 45 | test("reports an error in onExit", () { 46 | var chrome = new Chrome(Uri.parse("http://dart-lang.org"), 47 | settings: new ExecutableSettings( 48 | linuxExecutable: "_does_not_exist", 49 | macOSExecutable: "_does_not_exist", 50 | windowsExecutable: "_does_not_exist")); 51 | expect( 52 | chrome.onExit, 53 | throwsA(isApplicationException( 54 | startsWith("Failed to run Chrome: $noSuchFileMessage")))); 55 | }); 56 | 57 | test("can run successful tests", () async { 58 | await d.file("test.dart", """ 59 | import 'package:test/test.dart'; 60 | 61 | void main() { 62 | test("success", () {}); 63 | } 64 | """).create(); 65 | 66 | var test = await runTest(["-p", "chrome", "test.dart"]); 67 | expect(test.stdout, emitsThrough(contains("+1: All tests passed!"))); 68 | await test.shouldExit(0); 69 | }); 70 | 71 | test("can run failing tests", () async { 72 | await d.file("test.dart", """ 73 | import 'package:test/test.dart'; 74 | 75 | void main() { 76 | test("failure", () => throw new TestFailure("oh no")); 77 | } 78 | """).create(); 79 | 80 | var test = await runTest(["-p", "chrome", "test.dart"]); 81 | expect(test.stdout, emitsThrough(contains("-1: Some tests failed."))); 82 | await test.shouldExit(1); 83 | }); 84 | } 85 | -------------------------------------------------------------------------------- /test/runner/set_up_all_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn("vm") 6 | 7 | import 'package:test_descriptor/test_descriptor.dart' as d; 8 | 9 | import 'package:test/test.dart'; 10 | 11 | import '../io.dart'; 12 | 13 | void main() { 14 | test("an error causes the run to fail", () async { 15 | await d.file("test.dart", r""" 16 | import 'package:test/test.dart'; 17 | 18 | void main() { 19 | setUpAll(() => throw "oh no"); 20 | 21 | test("test", () {}); 22 | } 23 | """).create(); 24 | 25 | var test = await runTest(["test.dart"]); 26 | expect(test.stdout, emitsThrough(contains("-1: (setUpAll) [E]"))); 27 | expect(test.stdout, emitsThrough(contains("-1: Some tests failed."))); 28 | await test.shouldExit(1); 29 | }); 30 | 31 | test("doesn't run if no tests in the group are selected", () async { 32 | await d.file("test.dart", r""" 33 | import 'package:test/test.dart'; 34 | 35 | void main() { 36 | group("with setUpAll", () { 37 | setUpAll(() => throw "oh no"); 38 | 39 | test("test", () {}); 40 | }); 41 | 42 | group("without setUpAll", () { 43 | test("test", () {}); 44 | }); 45 | } 46 | """).create(); 47 | 48 | var test = await runTest(["test.dart", "--name", "without"]); 49 | expect(test.stdout, neverEmits(contains("(setUpAll)"))); 50 | await test.shouldExit(0); 51 | }); 52 | 53 | test("doesn't run if no tests in the group match the platform", () async { 54 | await d.file("test.dart", r""" 55 | import 'package:test/test.dart'; 56 | 57 | void main() { 58 | group("group", () { 59 | setUpAll(() => throw "oh no"); 60 | 61 | test("with", () {}, testOn: "browser"); 62 | }); 63 | 64 | group("group", () { 65 | test("without", () {}); 66 | }); 67 | } 68 | """).create(); 69 | 70 | var test = await runTest(["test.dart"]); 71 | expect(test.stdout, neverEmits(contains("(setUpAll)"))); 72 | await test.shouldExit(0); 73 | }); 74 | 75 | test("doesn't run if the group doesn't match the platform", () async { 76 | await d.file("test.dart", r""" 77 | import 'package:test/test.dart'; 78 | 79 | void main() { 80 | group("group", () { 81 | setUpAll(() => throw "oh no"); 82 | 83 | test("with", () {}); 84 | }, testOn: "browser"); 85 | 86 | group("group", () { 87 | test("without", () {}); 88 | }); 89 | } 90 | """).create(); 91 | 92 | var test = await runTest(["test.dart"]); 93 | expect(test.stdout, neverEmits(contains("(setUpAll)"))); 94 | await test.shouldExit(0); 95 | }); 96 | } 97 | -------------------------------------------------------------------------------- /test/util/path_handler_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:shelf/shelf.dart' as shelf; 8 | import 'package:test/src/util/path_handler.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | void main() { 12 | PathHandler handler; 13 | setUp(() => handler = new PathHandler()); 14 | 15 | _handle(shelf.Request request) => 16 | new Future.sync(() => handler.handler(request)); 17 | 18 | test("returns a 404 for a root URL", () async { 19 | var request = new shelf.Request("GET", Uri.parse("http://localhost/")); 20 | expect((await _handle(request)).statusCode, equals(404)); 21 | }); 22 | 23 | test("returns a 404 for an unregistered URL", () async { 24 | var request = new shelf.Request("GET", Uri.parse("http://localhost/foo")); 25 | expect((await _handle(request)).statusCode, equals(404)); 26 | }); 27 | 28 | test("runs a handler for an exact URL", () async { 29 | var request = new shelf.Request("GET", Uri.parse("http://localhost/foo")); 30 | handler.add("foo", expectAsync1((request) { 31 | expect(request.handlerPath, equals('/foo')); 32 | expect(request.url.path, isEmpty); 33 | return new shelf.Response.ok("good job!"); 34 | })); 35 | 36 | var response = await _handle(request); 37 | expect(response.statusCode, equals(200)); 38 | expect(response.readAsString(), completion(equals("good job!"))); 39 | }); 40 | 41 | test("runs a handler for a suffix", () async { 42 | var request = 43 | new shelf.Request("GET", Uri.parse("http://localhost/foo/bar")); 44 | handler.add("foo", expectAsync1((request) { 45 | expect(request.handlerPath, equals('/foo/')); 46 | expect(request.url.path, 'bar'); 47 | return new shelf.Response.ok("good job!"); 48 | })); 49 | 50 | var response = await _handle(request); 51 | expect(response.statusCode, equals(200)); 52 | expect(response.readAsString(), completion(equals("good job!"))); 53 | }); 54 | 55 | test("runs the longest matching handler", () async { 56 | var request = 57 | new shelf.Request("GET", Uri.parse("http://localhost/foo/bar/baz")); 58 | 59 | handler.add( 60 | "foo", 61 | expectAsync1((_) { 62 | return null; 63 | }, count: 0)); 64 | handler.add("foo/bar", expectAsync1((request) { 65 | expect(request.handlerPath, equals('/foo/bar/')); 66 | expect(request.url.path, 'baz'); 67 | return new shelf.Response.ok("good job!"); 68 | })); 69 | handler.add( 70 | "foo/bar/baz/bang", 71 | expectAsync1((_) { 72 | return null; 73 | }, count: 0)); 74 | 75 | var response = await _handle(request); 76 | expect(response.statusCode, equals(200)); 77 | expect(response.readAsString(), completion(equals("good job!"))); 78 | }); 79 | } 80 | -------------------------------------------------------------------------------- /lib/src/backend/operating_system.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// An enum of all operating systems supported by Dart. 6 | /// 7 | /// This is used for selecting which operating systems a test can run on. Even 8 | /// for browser tests, this indicates the operating system of the machine 9 | /// running the test runner. 10 | class OperatingSystem { 11 | /// Microsoft Windows. 12 | static const windows = const OperatingSystem._("Windows", "windows"); 13 | 14 | /// Mac OS X. 15 | static const macOS = const OperatingSystem._("OS X", "mac-os"); 16 | 17 | /// GNU/Linux. 18 | static const linux = const OperatingSystem._("Linux", "linux"); 19 | 20 | /// Android. 21 | /// 22 | /// Since this is the operating system the test runner is running on, this 23 | /// won't be true when testing remotely on an Android browser. 24 | static const android = const OperatingSystem._("Android", "android"); 25 | 26 | /// iOS. 27 | /// 28 | /// Since this is the operating system the test runner is running on, this 29 | /// won't be true when testing remotely on an iOS browser. 30 | static const iOS = const OperatingSystem._("iOS", "ios"); 31 | 32 | /// No operating system. 33 | /// 34 | /// This is used when running in the browser, or if an unrecognized operating 35 | /// system is used. It can't be referenced by name in platform selectors. 36 | static const none = const OperatingSystem._("none", "none"); 37 | 38 | /// A list of all instances of [OperatingSystem] other than [none]. 39 | static const all = const [windows, macOS, linux, android, iOS]; 40 | 41 | /// Finds an operating system by its name. 42 | /// 43 | /// If no operating system is found, returns [none]. 44 | static OperatingSystem find(String identifier) => 45 | all.firstWhere((platform) => platform.identifier == identifier, 46 | orElse: () => null); 47 | 48 | /// Finds an operating system by the return value from `dart:io`'s 49 | /// `Platform.operatingSystem`. 50 | /// 51 | /// If no operating system is found, returns [none]. 52 | static OperatingSystem findByIoName(String name) { 53 | switch (name) { 54 | case "windows": 55 | return windows; 56 | case "macos": 57 | return macOS; 58 | case "linux": 59 | return linux; 60 | case "android": 61 | return android; 62 | case "ios": 63 | return iOS; 64 | default: 65 | return none; 66 | } 67 | } 68 | 69 | /// The human-friendly of the operating system. 70 | final String name; 71 | 72 | /// The identifier used to look up the operating system. 73 | final String identifier; 74 | 75 | /// Whether this is a POSIX-ish operating system. 76 | bool get isPosix => this != windows && this != none; 77 | 78 | const OperatingSystem._(this.name, this.identifier); 79 | 80 | String toString() => name; 81 | } 82 | -------------------------------------------------------------------------------- /lib/src/backend/group.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:stack_trace/stack_trace.dart'; 6 | 7 | import 'group_entry.dart'; 8 | import 'metadata.dart'; 9 | import 'suite_platform.dart'; 10 | import 'test.dart'; 11 | 12 | /// A group contains one or more tests and subgroups. 13 | /// 14 | /// It includes metadata that applies to all contained tests. 15 | class Group implements GroupEntry { 16 | final String name; 17 | 18 | final Metadata metadata; 19 | 20 | final Trace trace; 21 | 22 | /// The children of this group. 23 | final List entries; 24 | 25 | /// Returns a new root-level group. 26 | Group.root(Iterable entries, {Metadata metadata}) 27 | : this(null, entries, metadata: metadata); 28 | 29 | /// A test to run before all tests in the group. 30 | /// 31 | /// This is `null` if no `setUpAll` callbacks were declared. 32 | final Test setUpAll; 33 | 34 | /// A test to run after all tests in the group. 35 | /// 36 | /// This is `null` if no `tearDown` callbacks were declared. 37 | final Test tearDownAll; 38 | 39 | /// The number of tests (recursively) in this group. 40 | int get testCount { 41 | if (_testCount != null) return _testCount; 42 | _testCount = entries.fold( 43 | 0, (count, entry) => count + (entry is Group ? entry.testCount : 1)); 44 | return _testCount; 45 | } 46 | 47 | int _testCount; 48 | 49 | Group(this.name, Iterable entries, 50 | {Metadata metadata, this.trace, this.setUpAll, this.tearDownAll}) 51 | : entries = new List.unmodifiable(entries), 52 | metadata = metadata == null ? new Metadata() : metadata; 53 | 54 | Group forPlatform(SuitePlatform platform) { 55 | if (!metadata.testOn.evaluate(platform)) return null; 56 | var newMetadata = metadata.forPlatform(platform); 57 | var filtered = _map((entry) => entry.forPlatform(platform)); 58 | if (filtered.isEmpty && entries.isNotEmpty) return null; 59 | return new Group(name, filtered, 60 | metadata: newMetadata, 61 | trace: trace, 62 | setUpAll: setUpAll, 63 | tearDownAll: tearDownAll); 64 | } 65 | 66 | Group filter(bool callback(Test test)) { 67 | var filtered = _map((entry) => entry.filter(callback)); 68 | if (filtered.isEmpty && entries.isNotEmpty) return null; 69 | return new Group(name, filtered, 70 | metadata: metadata, 71 | trace: trace, 72 | setUpAll: setUpAll, 73 | tearDownAll: tearDownAll); 74 | } 75 | 76 | /// Returns the entries of this group mapped using [callback]. 77 | /// 78 | /// Any `null` values returned by [callback] will be removed. 79 | List _map(GroupEntry callback(GroupEntry entry)) { 80 | return entries 81 | .map((entry) => callback(entry)) 82 | .where((entry) => entry != null) 83 | .toList(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /test/util/one_off_handler_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:shelf/shelf.dart' as shelf; 8 | import 'package:test/src/util/one_off_handler.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | void main() { 12 | OneOffHandler handler; 13 | setUp(() => handler = new OneOffHandler()); 14 | 15 | _handle(shelf.Request request) => 16 | new Future.sync(() => handler.handler(request)); 17 | 18 | test("returns a 404 for a root URL", () async { 19 | var request = new shelf.Request("GET", Uri.parse("http://localhost/")); 20 | expect((await _handle(request)).statusCode, equals(404)); 21 | }); 22 | 23 | test("returns a 404 for an unhandled URL", () async { 24 | var request = new shelf.Request("GET", Uri.parse("http://localhost/1")); 25 | expect((await _handle(request)).statusCode, equals(404)); 26 | }); 27 | 28 | test("passes a request to a handler only once", () async { 29 | var path = handler.create(expectAsync1((request) { 30 | expect(request.method, equals("GET")); 31 | return new shelf.Response.ok("good job!"); 32 | })); 33 | 34 | var request = new shelf.Request("GET", Uri.parse("http://localhost/$path")); 35 | var response = await _handle(request); 36 | expect(response.statusCode, equals(200)); 37 | expect(response.readAsString(), completion(equals("good job!"))); 38 | 39 | request = new shelf.Request("GET", Uri.parse("http://localhost/$path")); 40 | expect((await _handle(request)).statusCode, equals(404)); 41 | }); 42 | 43 | test("passes requests to the correct handlers", () async { 44 | var path1 = handler.create(expectAsync1((request) { 45 | expect(request.method, equals("GET")); 46 | return new shelf.Response.ok("one"); 47 | })); 48 | 49 | var path2 = handler.create(expectAsync1((request) { 50 | expect(request.method, equals("GET")); 51 | return new shelf.Response.ok("two"); 52 | })); 53 | 54 | var path3 = handler.create(expectAsync1((request) { 55 | expect(request.method, equals("GET")); 56 | return new shelf.Response.ok("three"); 57 | })); 58 | 59 | var request = 60 | new shelf.Request("GET", Uri.parse("http://localhost/$path2")); 61 | var response = await _handle(request); 62 | expect(response.statusCode, equals(200)); 63 | expect(response.readAsString(), completion(equals("two"))); 64 | 65 | request = new shelf.Request("GET", Uri.parse("http://localhost/$path1")); 66 | response = await _handle(request); 67 | expect(response.statusCode, equals(200)); 68 | expect(response.readAsString(), completion(equals("one"))); 69 | 70 | request = new shelf.Request("GET", Uri.parse("http://localhost/$path3")); 71 | response = await _handle(request); 72 | expect(response.statusCode, equals(200)); 73 | expect(response.readAsString(), completion(equals("three"))); 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /lib/src/runner/plugin/remote_platform_helpers.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:stream_channel/stream_channel.dart'; 8 | 9 | import '../../backend/stack_trace_formatter.dart'; 10 | import '../../util/stack_trace_mapper.dart'; 11 | import '../remote_listener.dart'; 12 | import '../suite_channel_manager.dart'; 13 | 14 | /// Returns a channel that will emit a serialized representation of the tests 15 | /// defined in [getMain]. 16 | /// 17 | /// This channel is used to control the tests. Platform plugins should forward 18 | /// it to the return value of [PlatformPlugin.loadChannel]. It's guaranteed to 19 | /// communicate using only JSON-serializable values. 20 | /// 21 | /// Any errors thrown within [getMain], synchronously or not, will be forwarded 22 | /// to the load test for this suite. Prints will similarly be forwarded to that 23 | /// test's print stream. 24 | /// 25 | /// If [hidePrints] is `true` (the default), calls to `print()` within this 26 | /// suite will not be forwarded to the parent zone's print handler. However, the 27 | /// caller may want them to be forwarded in (for example) a browser context 28 | /// where they'll be visible in the development console. 29 | /// 30 | /// If [beforeLoad] is passed, it's called before the tests have been declared 31 | /// for this worker. 32 | StreamChannel serializeSuite(Function getMain(), 33 | {bool hidePrints = true, Future beforeLoad()}) => 34 | RemoteListener.start(getMain, 35 | hidePrints: hidePrints, beforeLoad: beforeLoad); 36 | 37 | /// Returns a channel that communicates with a plugin in the test runner. 38 | /// 39 | /// This connects to a channel created by code in the test runner calling 40 | /// `RunnerSuite.channel()` with the same name. It can be used used to send and 41 | /// receive any JSON-serializable object. 42 | /// 43 | /// Throws a [StateError] if [name] has already been used for a channel, or if 44 | /// this is called outside a worker context (such as within a running test or 45 | /// `serializeSuite()`'s `onLoad()` function). 46 | StreamChannel suiteChannel(String name) { 47 | var manager = SuiteChannelManager.current; 48 | if (manager == null) { 49 | throw new StateError( 50 | 'suiteChannel() may only be called within a test worker.'); 51 | } 52 | 53 | return manager.connectOut(name); 54 | } 55 | 56 | /// Sets the stack trace mapper for the current test suite. 57 | /// 58 | /// This is used to convert JavaScript stack traces into their Dart equivalents 59 | /// using source maps. It should be set before any tests run, usually in the 60 | /// `onLoad()` callback to [serializeSuite]. 61 | void setStackTraceMapper(StackTraceMapper mapper) { 62 | var formatter = StackTraceFormatter.current; 63 | if (formatter == null) { 64 | throw new StateError( 65 | 'setStackTraceMapper() may only be called within a test worker.'); 66 | } 67 | 68 | formatter.configure(mapper: mapper); 69 | } 70 | -------------------------------------------------------------------------------- /lib/src/backend/stack_trace_formatter.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:stack_trace/stack_trace.dart'; 8 | 9 | import '../util/stack_trace_mapper.dart'; 10 | import 'invoker.dart'; 11 | 12 | /// The key used to look up [StackTraceFormatter.current] in a zone. 13 | final _currentKey = new Object(); 14 | 15 | /// A class that tracks how to format a stack trace according to the user's 16 | /// configuration. 17 | /// 18 | /// This can convert JavaScript stack traces to Dart using source maps, and fold 19 | /// irrelevant frames out of the stack trace. 20 | class StackTraceFormatter { 21 | /// A class that converts [trace] into a Dart stack trace, or `null` to use it 22 | /// as-is. 23 | StackTraceMapper _mapper; 24 | 25 | /// The list of packages to fold when producing terse [Chain]s. 26 | var _except = new Set.from(['test', 'stream_channel']); 27 | 28 | /// If non-empty, all packages not in this list will be folded when producing 29 | /// terse [Chain]s. 30 | var _only = new Set(); 31 | 32 | /// Returns the current manager, or `null` if this isn't called within a call 33 | /// to [asCurrent]. 34 | static StackTraceFormatter get current => 35 | Zone.current[_currentKey] as StackTraceFormatter; 36 | 37 | /// Runs [body] with [this] as [StackTraceFormatter.current]. 38 | /// 39 | /// This is zone-scoped, so [this] will be the current configuration in any 40 | /// asynchronous callbacks transitively created by [body]. 41 | T asCurrent(T body()) => runZoned(body, zoneValues: {_currentKey: this}); 42 | 43 | /// Configure how stack traces are formatted. 44 | /// 45 | /// The [mapper] is used to convert JavaScript traces into Dart traces. The 46 | /// [except] set indicates packages whose frames should be folded away. If 47 | /// [only] is non-empty, it indicates packages whose frames should *not* be 48 | /// folded away. 49 | void configure( 50 | {StackTraceMapper mapper, Set except, Set only}) { 51 | if (mapper != null) _mapper = mapper; 52 | if (except != null) _except = except; 53 | if (only != null) _only = only; 54 | } 55 | 56 | /// Converts [stackTrace] to a [Chain] and formats it according to the user's 57 | /// preferences. 58 | /// 59 | /// If [verbose] is `true`, this doesn't fold out irrelevant stack frames. It 60 | /// defaults to the current test's [Metadata.verboseTrace] configuration, or 61 | /// `false` if there is no current test. 62 | Chain formatStackTrace(StackTrace stackTrace, {bool verbose}) { 63 | verbose ??= 64 | Invoker.current?.liveTest?.test?.metadata?.verboseTrace ?? false; 65 | 66 | var chain = 67 | new Chain.forTrace(_mapper?.mapStackTrace(stackTrace) ?? stackTrace); 68 | if (verbose) return chain; 69 | 70 | return chain.foldFrames((frame) { 71 | if (_only.isNotEmpty) return !_only.contains(frame.package); 72 | return _except.contains(frame.package); 73 | }, terse: true); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /test/runner/browser/code_server.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:http_multi_server/http_multi_server.dart'; 8 | import 'package:shelf/shelf.dart' as shelf; 9 | import 'package:shelf/shelf_io.dart' as shelf_io; 10 | import 'package:shelf_test_handler/shelf_test_handler.dart'; 11 | import 'package:shelf_web_socket/shelf_web_socket.dart'; 12 | import 'package:web_socket_channel/web_socket_channel.dart'; 13 | 14 | /// A class that serves Dart and/or JS code and receives WebSocket connections. 15 | class CodeServer { 16 | /// The handler for [_server]. 17 | final ShelfTestHandler _handler; 18 | 19 | /// The URL of the server (including the port). 20 | final Uri url; 21 | 22 | static Future start() async { 23 | var server = await HttpMultiServer.loopback(0); 24 | var handler = new ShelfTestHandler(); 25 | shelf_io.serveRequests(server, (request) { 26 | if (request.method == "GET" && request.url.path == "favicon.ico") { 27 | return new shelf.Response.notFound(null); 28 | } else { 29 | return handler(request); 30 | } 31 | }); 32 | 33 | return new CodeServer._( 34 | handler, Uri.parse("http://localhost:${server.port}")); 35 | } 36 | 37 | CodeServer._(this._handler, this.url); 38 | 39 | /// Sets up a handler for the root of the server, "/", that serves a basic 40 | /// HTML page with a script tag that will run [dart]. 41 | void handleDart(String dart) { 42 | _handler.expect("GET", "/", (_) { 43 | return new shelf.Response.ok(""" 44 | 45 | 46 | 47 | 48 | 49 | 50 | """, headers: {'content-type': 'text/html'}); 51 | }); 52 | 53 | _handler.expect("GET", "/index.dart", (_) { 54 | return new shelf.Response.ok(''' 55 | import "dart:html"; 56 | 57 | main() async { 58 | $dart 59 | } 60 | ''', headers: {'content-type': 'application/dart'}); 61 | }); 62 | } 63 | 64 | /// Sets up a handler for the root of the server, "/", that serves a basic 65 | /// HTML page with a script tag that will run [javaScript]. 66 | void handleJavaScript(String javaScript) { 67 | _handler.expect("GET", "/", (_) { 68 | return new shelf.Response.ok(""" 69 | 70 | 71 | 72 | 73 | 74 | 75 | """, headers: {'content-type': 'text/html'}); 76 | }); 77 | 78 | _handler.expect("GET", "/index.js", (_) { 79 | return new shelf.Response.ok(javaScript, 80 | headers: {'content-type': 'application/javascript'}); 81 | }); 82 | } 83 | 84 | /// Handles a WebSocket connection to the root of the server, and returns a 85 | /// future that will complete to the WebSocket. 86 | Future handleWebSocket() { 87 | var completer = new Completer(); 88 | _handler.expect("GET", "/", webSocketHandler(completer.complete)); 89 | return completer.future; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /lib/src/runner/browser/phantom_js.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:io'; 7 | 8 | import 'package:path/path.dart' as p; 9 | 10 | import '../../backend/runtime.dart'; 11 | import '../../util/exit_codes.dart' as exit_codes; 12 | import '../../util/io.dart'; 13 | import '../application_exception.dart'; 14 | import '../executable_settings.dart'; 15 | import 'browser.dart'; 16 | import 'default_settings.dart'; 17 | 18 | /// The PhantomJS script that opens the host page. 19 | final _script = """ 20 | var system = require('system'); 21 | var page = require('webpage').create(); 22 | 23 | // PhantomJS versions older than 2.0.0 don't support the latest WebSocket spec. 24 | if (phantom.version.major < 2) phantom.exit(${exit_codes.protocol}); 25 | 26 | // Pipe browser messages to the process's stdout. This isn't used by default, 27 | // but it can be useful for debugging. 28 | page.onConsoleMessage = function(message) { 29 | console.log(message); 30 | } 31 | 32 | page.open(system.args[1], function(status) { 33 | if (status !== "success") phantom.exit(1); 34 | }); 35 | """; 36 | 37 | /// A class for running an instance of PhantomJS. 38 | /// 39 | /// Any errors starting or running the process are reported through [onExit]. 40 | class PhantomJS extends Browser { 41 | final name = "PhantomJS"; 42 | 43 | final Future remoteDebuggerUrl; 44 | 45 | factory PhantomJS(url, {ExecutableSettings settings, bool debug = false}) { 46 | settings ??= defaultSettings[Runtime.phantomJS]; 47 | var remoteDebuggerCompleter = new Completer.sync(); 48 | return new PhantomJS._(() async { 49 | var dir = createTempDir(); 50 | var script = p.join(dir, "script.js"); 51 | new File(script).writeAsStringSync(_script); 52 | 53 | var port = debug ? await getUnsafeUnusedPort() : null; 54 | 55 | var args = settings.arguments.toList(); 56 | if (debug) { 57 | args.addAll( 58 | ["--remote-debugger-port=$port", "--remote-debugger-autorun=yes"]); 59 | } 60 | args.addAll([script, url.toString()]); 61 | var process = await Process.start(settings.executable, args); 62 | 63 | // PhantomJS synchronously emits standard output, which means that if we 64 | // don't drain its stdout stream it can deadlock. 65 | process.stdout.listen((_) {}); 66 | 67 | process.exitCode.then((exitCode) { 68 | new Directory(dir).deleteSync(recursive: true); 69 | 70 | if (exitCode == exit_codes.protocol) { 71 | throw new ApplicationException( 72 | "Only PhantomJS version 2.0.0 or greater is supported"); 73 | } 74 | }); 75 | 76 | if (port != null) { 77 | remoteDebuggerCompleter.complete(Uri.parse( 78 | "http://localhost:$port/webkit/inspector/inspector.html?page=2")); 79 | } else { 80 | remoteDebuggerCompleter.complete(null); 81 | } 82 | 83 | return process; 84 | }, remoteDebuggerCompleter.future); 85 | } 86 | 87 | PhantomJS._(Future startBrowser(), this.remoteDebuggerUrl) 88 | : super(startBrowser); 89 | } 90 | -------------------------------------------------------------------------------- /lib/src/util/dart.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:convert'; 7 | import 'dart:isolate'; 8 | 9 | import 'package:analyzer/analyzer.dart'; 10 | import 'package:package_resolver/package_resolver.dart'; 11 | import 'package:source_span/source_span.dart'; 12 | 13 | import 'string_literal_iterator.dart'; 14 | 15 | /// Runs [code] in an isolate. 16 | /// 17 | /// [code] should be the contents of a Dart entrypoint. It may contain imports; 18 | /// they will be resolved in the same context as the host isolate. [message] is 19 | /// passed to the [main] method of the code being run; the caller is responsible 20 | /// for using this to establish communication with the isolate. 21 | /// 22 | /// If [resolver] is passed, its package resolution strategy is used to resolve 23 | /// code in the spawned isolate. It defaults to [PackageResolver.current]. 24 | Future runInIsolate(String code, message, 25 | {PackageResolver resolver, bool checked, SendPort onExit}) async { 26 | resolver ??= PackageResolver.current; 27 | return await Isolate.spawnUri( 28 | new Uri.dataFromString(code, 29 | mimeType: 'application/dart', encoding: utf8), 30 | [], 31 | message, 32 | packageConfig: await resolver.packageConfigUri, 33 | checked: checked, 34 | onExit: onExit); 35 | } 36 | 37 | // TODO(nweiz): Move this into the analyzer once it starts using SourceSpan 38 | // (issue 22977). 39 | /// Takes a span whose source is the value of a string that has been parsed from 40 | /// a Dart file and returns the corresponding span from within that Dart file. 41 | /// 42 | /// For example, suppose a Dart file contains `@Eval("1 + a")`. The 43 | /// [StringLiteral] `"1 + a"` is extracted; this is [context]. Its contents are 44 | /// then parsed, producing an error pointing to [span]: 45 | /// 46 | /// line 1, column 5: 47 | /// 1 + a 48 | /// ^ 49 | /// 50 | /// This span isn't very useful, since it only shows the location within the 51 | /// [StringLiteral]'s value. So it's passed to [contextualizeSpan] along with 52 | /// [context] and [file] (which contains the source of the entire Dart file), 53 | /// which then returns: 54 | /// 55 | /// line 4, column 12 of file.dart: 56 | /// @Eval("1 + a") 57 | /// ^ 58 | /// 59 | /// This properly handles multiline literals, adjacent literals, and literals 60 | /// containing escape sequences. It does not support interpolated literals. 61 | /// 62 | /// This will return `null` if [context] contains an invalid string or does not 63 | /// contain [span]. 64 | SourceSpan contextualizeSpan( 65 | SourceSpan span, StringLiteral context, SourceFile file) { 66 | var contextRunes = new StringLiteralIterator(context)..moveNext(); 67 | 68 | for (var i = 0; i < span.start.offset; i++) { 69 | if (!contextRunes.moveNext()) return null; 70 | } 71 | 72 | var start = contextRunes.offset; 73 | for (var spanRune in span.text.runes) { 74 | if (spanRune != contextRunes.current) return null; 75 | contextRunes.moveNext(); 76 | } 77 | 78 | return file.span(start, contextRunes.offset); 79 | } 80 | -------------------------------------------------------------------------------- /test/runner/timeout_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn("vm") 6 | 7 | import 'package:test_descriptor/test_descriptor.dart' as d; 8 | 9 | import 'package:test/test.dart'; 10 | 11 | import '../io.dart'; 12 | 13 | void main() { 14 | test("respects top-level @Timeout declarations", () async { 15 | await d.file("test.dart", ''' 16 | @Timeout(const Duration(seconds: 0)) 17 | 18 | import 'dart:async'; 19 | 20 | import 'package:test/test.dart'; 21 | 22 | void main() { 23 | test("timeout", () async { 24 | await new Future.delayed(Duration.zero); 25 | }); 26 | } 27 | ''').create(); 28 | 29 | var test = await runTest(["test.dart"]); 30 | expect( 31 | test.stdout, 32 | containsInOrder( 33 | ["Test timed out after 0 seconds.", "-1: Some tests failed."])); 34 | await test.shouldExit(1); 35 | }); 36 | 37 | test("respects the --timeout flag", () async { 38 | await d.file("test.dart", ''' 39 | import 'dart:async'; 40 | 41 | import 'package:test/test.dart'; 42 | 43 | void main() { 44 | test("timeout", () async { 45 | await new Future.delayed(Duration.zero); 46 | }); 47 | } 48 | ''').create(); 49 | 50 | var test = await runTest(["--timeout=0s", "test.dart"]); 51 | expect( 52 | test.stdout, 53 | containsInOrder( 54 | ["Test timed out after 0 seconds.", "-1: Some tests failed."])); 55 | await test.shouldExit(1); 56 | }); 57 | 58 | test("timeout is reset with each retry", () async { 59 | await d.file("test.dart", ''' 60 | import 'dart:async'; 61 | 62 | import 'package:test/test.dart'; 63 | 64 | void main() { 65 | var runCount = 0; 66 | test("timeout", () async { 67 | runCount++; 68 | if (runCount <=2) { 69 | await new Future.delayed(new Duration(milliseconds: 1000)); 70 | } 71 | }, retry: 3); 72 | } 73 | ''').create(); 74 | 75 | var test = await runTest(["--timeout=400ms", "test.dart"]); 76 | expect( 77 | test.stdout, 78 | containsInOrder([ 79 | "Test timed out after 0.4 seconds.", 80 | "Test timed out after 0.4 seconds.", 81 | "+1: All tests passed!" 82 | ])); 83 | await test.shouldExit(0); 84 | }); 85 | 86 | test("the --timeout flag applies on top of the default 30s timeout", 87 | () async { 88 | await d.file("test.dart", ''' 89 | import 'dart:async'; 90 | 91 | import 'package:test/test.dart'; 92 | 93 | void main() { 94 | test("no timeout", () async { 95 | await new Future.delayed(new Duration(milliseconds: 250)); 96 | }); 97 | 98 | test("timeout", () async { 99 | await new Future.delayed(new Duration(milliseconds: 750)); 100 | }); 101 | } 102 | ''').create(); 103 | 104 | // This should make the timeout about 500ms, which should cause exactly one 105 | // test to fail. 106 | var test = await runTest(["--timeout=0.016x", "test.dart"]); 107 | expect( 108 | test.stdout, 109 | containsInOrder( 110 | ["Test timed out after 0.4 seconds.", "-1: Some tests failed."])); 111 | await test.shouldExit(1); 112 | }); 113 | } 114 | -------------------------------------------------------------------------------- /lib/src/runner/browser/chrome.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:io'; 7 | 8 | import '../../backend/runtime.dart'; 9 | import '../../util/io.dart'; 10 | import '../executable_settings.dart'; 11 | import 'browser.dart'; 12 | import 'default_settings.dart'; 13 | 14 | // TODO(nweiz): move this into its own package? 15 | /// A class for running an instance of Chrome. 16 | /// 17 | /// Most of the communication with the browser is expected to happen via HTTP, 18 | /// so this exposes a bare-bones API. The browser starts as soon as the class is 19 | /// constructed, and is killed when [close] is called. 20 | /// 21 | /// Any errors starting or running the process are reported through [onExit]. 22 | class Chrome extends Browser { 23 | final name = "Chrome"; 24 | 25 | final Future remoteDebuggerUrl; 26 | 27 | /// Starts a new instance of Chrome open to the given [url], which may be a 28 | /// [Uri] or a [String]. 29 | factory Chrome(Uri url, {ExecutableSettings settings, bool debug = false}) { 30 | settings ??= defaultSettings[Runtime.chrome]; 31 | var remoteDebuggerCompleter = new Completer.sync(); 32 | return new Chrome._(() async { 33 | var tryPort = ([int port]) async { 34 | var dir = createTempDir(); 35 | var args = [ 36 | "--user-data-dir=$dir", 37 | url.toString(), 38 | "--disable-extensions", 39 | "--disable-popup-blocking", 40 | "--bwsi", 41 | "--no-first-run", 42 | "--no-default-browser-check", 43 | "--disable-default-apps", 44 | "--disable-translate", 45 | ]; 46 | 47 | if (!debug && settings.headless) { 48 | args.addAll([ 49 | "--headless", 50 | "--disable-gpu", 51 | // We don't actually connect to the remote debugger, but Chrome will 52 | // close as soon as the page is loaded if we don't turn it on. 53 | "--remote-debugging-port=0" 54 | ]); 55 | } 56 | 57 | args.addAll(settings.arguments); 58 | 59 | // Currently, Chrome doesn't provide any way of ensuring that this port 60 | // was successfully bound. It produces an error if the binding fails, 61 | // but without a reliable and fast way to tell if it succeeded that 62 | // doesn't provide us much. It's very unlikely that this port will fail, 63 | // though. 64 | if (port != null) args.add("--remote-debugging-port=$port"); 65 | 66 | var process = await Process.start(settings.executable, args); 67 | 68 | if (port != null) { 69 | remoteDebuggerCompleter.complete( 70 | getRemoteDebuggerUrl(Uri.parse("http://localhost:$port"))); 71 | } else { 72 | remoteDebuggerCompleter.complete(null); 73 | } 74 | 75 | process.exitCode 76 | .then((_) => new Directory(dir).deleteSync(recursive: true)); 77 | 78 | return process; 79 | }; 80 | 81 | if (!debug) return tryPort(); 82 | return getUnusedPort(tryPort); 83 | }, remoteDebuggerCompleter.future); 84 | } 85 | 86 | Chrome._(Future startBrowser(), this.remoteDebuggerUrl) 87 | : super(startBrowser); 88 | } 89 | -------------------------------------------------------------------------------- /lib/src/runner/hybrid_listener.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import "dart:async"; 6 | import "dart:isolate"; 7 | 8 | import "package:async/async.dart"; 9 | import "package:stack_trace/stack_trace.dart"; 10 | import "package:stream_channel/stream_channel.dart"; 11 | 12 | import "../util/remote_exception.dart"; 13 | import "../utils.dart"; 14 | 15 | /// A sink transformer that wraps data and error events so that errors can be 16 | /// decoded after being JSON-serialized. 17 | final _transformer = new StreamSinkTransformer.fromHandlers( 18 | handleData: (data, sink) { 19 | ensureJsonEncodable(data); 20 | sink.add({"type": "data", "data": data}); 21 | }, handleError: (error, stackTrace, sink) { 22 | sink.add( 23 | {"type": "error", "error": RemoteException.serialize(error, stackTrace)}); 24 | }); 25 | 26 | /// Runs the body of a hybrid isolate and communicates its messages, errors, and 27 | /// prints to the main isolate. 28 | /// 29 | /// The [getMain] function returns the `hybridMain()` method. It's wrapped in a 30 | /// closure so that, if the method undefined, we can catch the error and notify 31 | /// the caller of it. 32 | /// 33 | /// The [data] argument contains two values: a [SendPort] that communicates with 34 | /// the main isolate, and a message to pass to `hybridMain()`. 35 | void listen(Function getMain(), List data) { 36 | var channel = new IsolateChannel.connectSend(data.first as SendPort); 37 | var message = data.last; 38 | 39 | Chain.capture(() { 40 | runZoned(() { 41 | dynamic /*Function*/ main; 42 | try { 43 | main = getMain(); 44 | } on NoSuchMethodError catch (_) { 45 | _sendError(channel, "No top-level hybridMain() function defined."); 46 | return; 47 | } catch (error, stackTrace) { 48 | _sendError(channel, error, stackTrace); 49 | return; 50 | } 51 | 52 | if (main is! Function) { 53 | _sendError(channel, "Top-level hybridMain is not a function."); 54 | return; 55 | } else if (main is! Function(StreamChannel) && 56 | main is! Function(StreamChannel, Null)) { 57 | _sendError(channel, 58 | "Top-level hybridMain() function must take one or two arguments."); 59 | return; 60 | } 61 | 62 | // Wrap [channel] before passing it to user code so that we can wrap 63 | // errors and distinguish user data events from control events sent by the 64 | // listener. 65 | var transformedChannel = channel.transformSink(_transformer); 66 | if (main is Function(StreamChannel)) { 67 | main(transformedChannel); 68 | } else { 69 | main(transformedChannel, message); 70 | } 71 | }, zoneSpecification: new ZoneSpecification(print: (_, __, ___, line) { 72 | channel.sink.add({"type": "print", "line": line}); 73 | })); 74 | }, onError: (error, stackTrace) async { 75 | _sendError(channel, error, stackTrace); 76 | await channel.sink.close(); 77 | Zone.current.handleUncaughtError(error, stackTrace); 78 | }); 79 | } 80 | 81 | /// Sends a message over [channel] indicating an error from user code. 82 | void _sendError(StreamChannel channel, error, [StackTrace stackTrace]) { 83 | channel.sink.add({ 84 | "type": "error", 85 | "error": RemoteException.serialize(error, stackTrace ?? new Chain.current()) 86 | }); 87 | } 88 | -------------------------------------------------------------------------------- /lib/src/util/stack_trace_mapper.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:package_resolver/package_resolver.dart'; 6 | import 'package:source_map_stack_trace/source_map_stack_trace.dart' as mapper; 7 | import 'package:source_maps/source_maps.dart'; 8 | 9 | /// A class for mapping JS stack traces to Dart stack traces using source maps. 10 | class StackTraceMapper { 11 | /// The parsed source map. 12 | /// 13 | /// This is initialized lazily in `mapStackTrace()`. 14 | Mapping _mapping; 15 | 16 | /// The package resolution information passed to dart2js. 17 | final SyncPackageResolver _packageResolver; 18 | 19 | /// The URL of the SDK root from which dart2js loaded its sources. 20 | final Uri _sdkRoot; 21 | 22 | /// The contents of the source map. 23 | final String _mapContents; 24 | 25 | /// The URL of the source map. 26 | final Uri _mapUrl; 27 | 28 | StackTraceMapper(this._mapContents, 29 | {Uri mapUrl, SyncPackageResolver packageResolver, Uri sdkRoot}) 30 | : _mapUrl = mapUrl, 31 | _packageResolver = packageResolver, 32 | _sdkRoot = sdkRoot; 33 | 34 | /// Converts [trace] into a Dart stack trace. 35 | StackTrace mapStackTrace(StackTrace trace) { 36 | _mapping ??= parseExtended(_mapContents, mapUrl: _mapUrl); 37 | return mapper.mapStackTrace(_mapping, trace, 38 | packageResolver: _packageResolver, sdkRoot: _sdkRoot); 39 | } 40 | 41 | /// Returns a Map representation which is suitable for JSON serialization. 42 | Map serialize() { 43 | return { 44 | 'mapContents': _mapContents, 45 | 'sdkRoot': _sdkRoot?.toString(), 46 | 'packageConfigMap': 47 | _serializePackageConfigMap(_packageResolver.packageConfigMap), 48 | 'packageRoot': _packageResolver.packageRoot?.toString(), 49 | 'mapUrl': _mapUrl?.toString(), 50 | }; 51 | } 52 | 53 | /// Returns a [StackTraceMapper] contained in the provided serialized 54 | /// representation. 55 | static StackTraceMapper deserialize(Map serialized) { 56 | if (serialized == null) return null; 57 | String packageRoot = serialized['packageRoot'] as String ?? ''; 58 | return new StackTraceMapper(serialized['mapContents'] as String, 59 | sdkRoot: Uri.parse(serialized['sdkRoot'] as String), 60 | packageResolver: packageRoot.isNotEmpty 61 | ? new SyncPackageResolver.root( 62 | Uri.parse(serialized['packageRoot'] as String)) 63 | : new SyncPackageResolver.config(_deserializePackageConfigMap( 64 | (serialized['packageConfigMap'] as Map) 65 | .cast())), 66 | mapUrl: Uri.parse(serialized['mapUrl'] as String)); 67 | } 68 | 69 | /// Converts a [packageConfigMap] into a format suitable for JSON 70 | /// serialization. 71 | static Map _serializePackageConfigMap( 72 | Map packageConfigMap) { 73 | if (packageConfigMap == null) return null; 74 | return packageConfigMap.map((key, value) => new MapEntry(key, '$value')); 75 | } 76 | 77 | /// Converts a serialized package config map into a format suitable for 78 | /// the [PackageResolver] 79 | static Map _deserializePackageConfigMap( 80 | Map serialized) { 81 | if (serialized == null) return null; 82 | return serialized.map((key, value) => new MapEntry(key, Uri.parse(value))); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/src/runner/plugin/platform.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:stream_channel/stream_channel.dart'; 8 | 9 | import '../../backend/suite_platform.dart'; 10 | import '../configuration/suite.dart'; 11 | import '../environment.dart'; 12 | import '../runner_suite.dart'; 13 | import 'environment.dart'; 14 | import 'platform_helpers.dart'; 15 | 16 | /// A class that defines a platform for which test suites can be loaded. 17 | /// 18 | /// A minimal plugin must define [loadChannel], which connects to a client in 19 | /// which the tests are defined. This is enough to support most of the test 20 | /// runner's functionality. 21 | /// 22 | /// In order to support interactive debugging, a plugin must override [load] as 23 | /// well, which returns a [RunnerSuite] that can contain a custom [Environment] 24 | /// and control debugging metadata such as [RunnerSuite.isDebugging] and 25 | /// [RunnerSuite.onDebugging]. The plugin must create this suite by calling the 26 | /// [deserializeSuite] helper function. 27 | /// 28 | /// A platform plugin can be registered by passing it to [new Loader]'s 29 | /// `plugins` parameter. 30 | abstract class PlatformPlugin { 31 | /// Loads and establishes a connection with the test file at [path] using 32 | /// [platform]. 33 | /// 34 | /// This returns a channel that's connected to a remote client. The client 35 | /// must connect it to a channel returned by [serializeGroup]. The default 36 | /// implementation of [load] will take care of wrapping it up in a 37 | /// [RunnerSuite] and running the tests when necessary. 38 | /// 39 | /// The returned channel may emit exceptions, indicating that the suite failed 40 | /// to load or crashed later on. If the channel is closed by the caller, that 41 | /// indicates that the suite is no longer needed and its resources may be 42 | /// released. 43 | /// 44 | /// The `platform.platform` is guaranteed to be one of the platforms 45 | /// associated with this plugin in [new Loader]'s `plugins` parameter. 46 | // TODO(grouma) - Remove this method from the API as no platforms implement 47 | // it. 48 | StreamChannel loadChannel(String path, SuitePlatform platform); 49 | 50 | /// Loads the runner suite for the test file at [path] using [platform], with 51 | /// [suiteConfig] encoding the suite-specific configuration. 52 | /// 53 | /// By default, this just calls [loadChannel] and passes its result to 54 | /// [deserializeSuite]. However, it can be overridden to provide more 55 | /// fine-grained control over the [RunnerSuite], including providing a custom 56 | /// implementation of [Environment]. 57 | /// 58 | /// Subclasses overriding this method must call [deserializeSuite] in 59 | /// `platform_helpers.dart` to obtain a [RunnerSuiteController]. They must 60 | /// pass the opaque [message] parameter to the [deserializeSuite] call. 61 | Future load(String path, SuitePlatform platform, 62 | SuiteConfiguration suiteConfig, Object message) async { 63 | // loadChannel may throw an exception. That's fine; it will cause the 64 | // LoadSuite to emit an error, which will be presented to the user. 65 | var channel = loadChannel(path, platform); 66 | var controller = deserializeSuite( 67 | path, platform, suiteConfig, new PluginEnvironment(), channel, message); 68 | return await controller.suite; 69 | } 70 | 71 | Future closeEphemeral() async {} 72 | 73 | Future close() async {} 74 | } 75 | -------------------------------------------------------------------------------- /test/frontend/timeout_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:test/test.dart'; 6 | 7 | void main() { 8 | group("Timeout.parse", () { 9 | group('for "none"', () { 10 | test("successfully parses", () { 11 | expect(new Timeout.parse("none"), equals(Timeout.none)); 12 | }); 13 | 14 | test("rejects invalid input", () { 15 | expect(() => new Timeout.parse(" none"), throwsFormatException); 16 | expect(() => new Timeout.parse("none "), throwsFormatException); 17 | expect(() => new Timeout.parse("xnone"), throwsFormatException); 18 | expect(() => new Timeout.parse("nonex"), throwsFormatException); 19 | expect(() => new Timeout.parse("noxe"), throwsFormatException); 20 | }); 21 | }); 22 | 23 | group("for a relative timeout", () { 24 | test("successfully parses", () { 25 | expect(new Timeout.parse("1x"), equals(new Timeout.factor(1))); 26 | expect(new Timeout.parse("2.5x"), equals(new Timeout.factor(2.5))); 27 | expect(new Timeout.parse("1.2e3x"), equals(new Timeout.factor(1.2e3))); 28 | }); 29 | 30 | test("rejects invalid input", () { 31 | expect(() => new Timeout.parse(".x"), throwsFormatException); 32 | expect(() => new Timeout.parse("x"), throwsFormatException); 33 | expect(() => new Timeout.parse("ax"), throwsFormatException); 34 | expect(() => new Timeout.parse("1x "), throwsFormatException); 35 | expect(() => new Timeout.parse("1x5m"), throwsFormatException); 36 | }); 37 | }); 38 | 39 | group("for an absolute timeout", () { 40 | test("successfully parses all supported units", () { 41 | expect(new Timeout.parse("2d"), 42 | equals(new Timeout(new Duration(days: 2)))); 43 | expect(new Timeout.parse("2h"), 44 | equals(new Timeout(new Duration(hours: 2)))); 45 | expect(new Timeout.parse("2m"), 46 | equals(new Timeout(new Duration(minutes: 2)))); 47 | expect(new Timeout.parse("2s"), 48 | equals(new Timeout(new Duration(seconds: 2)))); 49 | expect(new Timeout.parse("2ms"), 50 | equals(new Timeout(new Duration(milliseconds: 2)))); 51 | expect(new Timeout.parse("2us"), 52 | equals(new Timeout(new Duration(microseconds: 2)))); 53 | }); 54 | 55 | test("supports non-integer units", () { 56 | expect(new Timeout.parse("2.73d"), 57 | equals(new Timeout(new Duration(days: 1) * 2.73))); 58 | }); 59 | 60 | test("supports multiple units", () { 61 | expect( 62 | new Timeout.parse("1d 2h3m 4s5ms\t6us"), 63 | equals(new Timeout(new Duration( 64 | days: 1, 65 | hours: 2, 66 | minutes: 3, 67 | seconds: 4, 68 | milliseconds: 5, 69 | microseconds: 6)))); 70 | }); 71 | 72 | test("rejects invalid input", () { 73 | expect(() => new Timeout.parse(".d"), throwsFormatException); 74 | expect(() => new Timeout.parse("d"), throwsFormatException); 75 | expect(() => new Timeout.parse("ad"), throwsFormatException); 76 | expect(() => new Timeout.parse("1z"), throwsFormatException); 77 | expect(() => new Timeout.parse("1u"), throwsFormatException); 78 | expect(() => new Timeout.parse("1d5x"), throwsFormatException); 79 | expect(() => new Timeout.parse("1d*5m"), throwsFormatException); 80 | }); 81 | }); 82 | }); 83 | } 84 | -------------------------------------------------------------------------------- /test/runner/tear_down_all_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn("vm") 6 | 7 | import 'package:test_descriptor/test_descriptor.dart' as d; 8 | 9 | import 'package:test/test.dart'; 10 | 11 | import '../io.dart'; 12 | 13 | void main() { 14 | test("an error causes the run to fail", () async { 15 | await d.file("test.dart", r""" 16 | import 'package:test/test.dart'; 17 | 18 | void main() { 19 | tearDownAll(() => throw "oh no"); 20 | 21 | test("test", () {}); 22 | } 23 | """).create(); 24 | 25 | var test = await runTest(["test.dart"]); 26 | expect(test.stdout, emitsThrough(contains("-1: (tearDownAll) [E]"))); 27 | expect(test.stdout, emitsThrough(contains("-1: Some tests failed."))); 28 | await test.shouldExit(1); 29 | }); 30 | 31 | test("doesn't run if no tests in the group are selected", () async { 32 | await d.file("test.dart", r""" 33 | import 'package:test/test.dart'; 34 | 35 | void main() { 36 | group("with tearDownAll", () { 37 | tearDownAll(() => throw "oh no"); 38 | 39 | test("test", () {}); 40 | }); 41 | 42 | group("without tearDownAll", () { 43 | test("test", () {}); 44 | }); 45 | } 46 | """).create(); 47 | 48 | var test = await runTest(["test.dart", "--name", "without"]); 49 | expect(test.stdout, neverEmits(contains("(tearDownAll)"))); 50 | await test.shouldExit(0); 51 | }); 52 | 53 | test("doesn't run if no tests in the group are selected", () async { 54 | await d.file("test.dart", r""" 55 | import 'package:test/test.dart'; 56 | 57 | void main() { 58 | group("group", () { 59 | tearDownAll(() => throw "oh no"); 60 | 61 | test("with", () {}); 62 | }); 63 | 64 | group("group", () { 65 | test("without", () {}); 66 | }); 67 | } 68 | """).create(); 69 | 70 | var test = await runTest(["test.dart", "--name", "without"]); 71 | expect(test.stdout, neverEmits(contains("(tearDownAll)"))); 72 | await test.shouldExit(0); 73 | }); 74 | 75 | test("doesn't run if no tests in the group match the platform", () async { 76 | await d.file("test.dart", r""" 77 | import 'package:test/test.dart'; 78 | 79 | void main() { 80 | group("group", () { 81 | tearDownAll(() => throw "oh no"); 82 | 83 | test("with", () {}, testOn: "browser"); 84 | }); 85 | 86 | group("group", () { 87 | test("without", () {}); 88 | }); 89 | } 90 | """).create(); 91 | 92 | var test = await runTest(["test.dart"]); 93 | expect(test.stdout, neverEmits(contains("(tearDownAll)"))); 94 | await test.shouldExit(0); 95 | }); 96 | 97 | test("doesn't run if the group doesn't match the platform", () async { 98 | await d.file("test.dart", r""" 99 | import 'package:test/test.dart'; 100 | 101 | void main() { 102 | group("group", () { 103 | tearDownAll(() => throw "oh no"); 104 | 105 | test("with", () {}); 106 | }, testOn: "browser"); 107 | 108 | group("group", () { 109 | test("without", () {}); 110 | }); 111 | } 112 | """).create(); 113 | 114 | var test = await runTest(["test.dart"]); 115 | expect(test.stdout, neverEmits(contains("(tearDownAll)"))); 116 | await test.shouldExit(0); 117 | }); 118 | } 119 | -------------------------------------------------------------------------------- /lib/src/runner/live_suite.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:collection/collection.dart'; 8 | 9 | import '../backend/live_test.dart'; 10 | import 'runner_suite.dart'; 11 | 12 | /// A view of the execution of a test suite. 13 | /// 14 | /// This is distinct from [Suite] because it represents the progress of running 15 | /// a suite rather than the suite's contents. It provides events and collections 16 | /// that give the caller a view into the suite's current state. 17 | abstract class LiveSuite { 18 | /// The suite that's being run. 19 | RunnerSuite get suite; 20 | 21 | /// Whether the suite has completed. 22 | /// 23 | /// Note that even if this returns `true`, the suite may still be running code 24 | /// asynchronously. A suite is considered complete once all of its tests are 25 | /// complete, but it's possible for a test to continue running even after it's 26 | /// been marked complete—see [LiveTest.isComplete] for details. 27 | /// 28 | /// The [isClosed] getter can be used to determine whether the suite and its 29 | /// tests are guaranteed to emit no more events. 30 | bool get isComplete; 31 | 32 | /// A [Future] that completes once the suite is complete. 33 | /// 34 | /// Note that even once this completes, the suite may still be running code 35 | /// asynchronously. A suite is considered complete once all of its tests are 36 | /// complete, but it's possible for a test to continue running even after it's 37 | /// been marked complete—see [LiveTest.isComplete] for details. 38 | /// 39 | /// The [onClose] future can be used to determine when the suite and its tests 40 | /// are guaranteed to emit no more events. 41 | Future get onComplete; 42 | 43 | /// Whether the suite has been closed. 44 | /// 45 | /// If this is `true`, no code is running for the suite or any of its tests. 46 | /// At this point, the caller can be sure that the suites' tests are all in 47 | /// fixed states that will not change in the future. 48 | bool get isClosed; 49 | 50 | /// A [Future] that completes when the suite has been closed. 51 | /// 52 | /// Once this completes, no code is running for the suite or any of its tests. 53 | /// At this point, the caller can be sure that the suites' tests are all in 54 | /// fixed states that will not change in the future. 55 | Future get onClose; 56 | 57 | /// All the currently-known tests in this suite that have run or are running. 58 | /// 59 | /// This is guaranteed to contain the same tests as the union of [passed], 60 | /// [skipped], [failed], and [active]. 61 | Set get liveTests { 62 | var sets = [passed, skipped, failed]; 63 | if (active != null) sets.add(new Set.from([active])); 64 | return new UnionSet.from(sets); 65 | } 66 | 67 | /// A stream that emits each [LiveTest] in this suite as it's about to start 68 | /// running. 69 | /// 70 | /// This is guaranteed to fire before [LiveTest.onStateChange] first fires. It 71 | /// will close once all tests the user has selected are run. 72 | Stream get onTestStarted; 73 | 74 | /// The set of tests in this suite that have completed and been marked as 75 | /// passing. 76 | Set get passed; 77 | 78 | /// The set of tests in this suite that have completed and been marked as 79 | /// skipped. 80 | Set get skipped; 81 | 82 | /// The set of tests in this suite that have completed and been marked as 83 | /// failing or error. 84 | Set get failed; 85 | 86 | /// The currently running test in this suite, or `null` if no test is running. 87 | LiveTest get active; 88 | } 89 | -------------------------------------------------------------------------------- /lib/src/runner/console.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | import 'dart:math' as math; 7 | 8 | import 'package:async/async.dart'; 9 | 10 | import '../util/io.dart'; 11 | import '../utils.dart'; 12 | 13 | /// An interactive console for taking user commands. 14 | class Console { 15 | /// The registered commands. 16 | final _commands = {}; 17 | 18 | /// The pending next line of standard input, if we're waiting on one. 19 | CancelableOperation _nextLine; 20 | 21 | /// Whether the console is currently running. 22 | bool _running = false; 23 | 24 | /// The terminal escape for red text, or the empty string if this is Windows 25 | /// or not outputting to a terminal. 26 | final String _red; 27 | 28 | /// The terminal escape for bold text, or the empty string if this is 29 | /// Windows or not outputting to a terminal. 30 | final String _bold; 31 | 32 | /// The terminal escape for removing test coloring, or the empty string if 33 | /// this is Windows or not outputting to a terminal. 34 | final String _noColor; 35 | 36 | /// Creates a new [Console]. 37 | /// 38 | /// If [color] is true, this uses Unix terminal colors. 39 | Console({bool color = true}) 40 | : _red = color ? '\u001b[31m' : '', 41 | _bold = color ? '\u001b[1m' : '', 42 | _noColor = color ? '\u001b[0m' : '' { 43 | registerCommand("help", "Displays this help information.", _displayHelp); 44 | } 45 | 46 | /// Registers a command to be run whenever the user types [name]. 47 | /// 48 | /// The [description] should be a one-line description of the command to print 49 | /// in the help output. The [body] callback will be called when the user types 50 | /// the command, and may return a [Future]. 51 | void registerCommand(String name, String description, body()) { 52 | if (_commands.containsKey(name)) { 53 | throw new ArgumentError( 54 | 'The console already has a command named "$name".'); 55 | } 56 | 57 | _commands[name] = new _Command(name, description, body); 58 | } 59 | 60 | /// Starts running the console. 61 | /// 62 | /// This prints the initial prompt and loops while waiting for user input. 63 | void start() { 64 | _running = true; 65 | invoke(() async { 66 | while (_running) { 67 | stdout.write("> "); 68 | _nextLine = stdinLines.cancelable((queue) => queue.next); 69 | var commandName = await _nextLine.value; 70 | _nextLine = null; 71 | 72 | var command = _commands[commandName]; 73 | if (command == null) { 74 | stderr.writeln( 75 | "${_red}Unknown command $_bold$commandName$_noColor$_red." 76 | "$_noColor"); 77 | } else { 78 | await command.body(); 79 | } 80 | } 81 | }); 82 | } 83 | 84 | /// Stops the console running. 85 | void stop() { 86 | _running = false; 87 | if (_nextLine != null) { 88 | stdout.writeln(); 89 | _nextLine.cancel(); 90 | } 91 | } 92 | 93 | /// Displays the help info for the console commands. 94 | void _displayHelp() { 95 | var maxCommandLength = 96 | _commands.values.map((command) => command.name.length).reduce(math.max); 97 | 98 | for (var command in _commands.values) { 99 | var name = command.name.padRight(maxCommandLength + 4); 100 | print("$_bold$name$_noColor${command.description}"); 101 | } 102 | } 103 | } 104 | 105 | /// An individual console command. 106 | class _Command { 107 | /// The name of the command. 108 | final String name; 109 | 110 | /// The single-line description of the command. 111 | final String description; 112 | 113 | /// The callback to run when the command is invoked. 114 | final Function body; 115 | 116 | _Command(this.name, this.description, this.body); 117 | } 118 | -------------------------------------------------------------------------------- /test/runner/configuration/global_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn("vm") 6 | import 'dart:convert'; 7 | 8 | import 'package:test_descriptor/test_descriptor.dart' as d; 9 | 10 | import 'package:test/src/util/exit_codes.dart' as exit_codes; 11 | import 'package:test/test.dart'; 12 | 13 | import '../../io.dart'; 14 | 15 | void main() { 16 | test("ignores an empty file", () async { 17 | await d.file("global_test.yaml", "").create(); 18 | 19 | await d.file("test.dart", """ 20 | import 'package:test/test.dart'; 21 | 22 | void main() { 23 | test("success", () {}); 24 | } 25 | """).create(); 26 | 27 | var test = await runTest(["test.dart"], 28 | environment: {"DART_TEST_CONFIG": "global_test.yaml"}); 29 | expect(test.stdout, emitsThrough(contains("+1: All tests passed!"))); 30 | await test.shouldExit(0); 31 | }); 32 | 33 | test("uses supported test configuration", () async { 34 | await d 35 | .file("global_test.yaml", jsonEncode({"verbose_trace": true})) 36 | .create(); 37 | 38 | await d.file("test.dart", """ 39 | import 'package:test/test.dart'; 40 | 41 | void main() { 42 | test("failure", () => throw "oh no"); 43 | } 44 | """).create(); 45 | 46 | var test = await runTest(["test.dart"], 47 | environment: {"DART_TEST_CONFIG": "global_test.yaml"}); 48 | expect(test.stdout, emitsThrough(contains("dart:async"))); 49 | await test.shouldExit(1); 50 | }); 51 | 52 | test("uses supported runner configuration", () async { 53 | await d.file("global_test.yaml", jsonEncode({"reporter": "json"})).create(); 54 | 55 | await d.file("test.dart", """ 56 | import 'package:test/test.dart'; 57 | 58 | void main() { 59 | test("success", () {}); 60 | } 61 | """).create(); 62 | 63 | var test = await runTest(["test.dart"], 64 | environment: {"DART_TEST_CONFIG": "global_test.yaml"}); 65 | expect(test.stdout, emitsThrough(contains('"testStart"'))); 66 | await test.shouldExit(0); 67 | }); 68 | 69 | test("local configuration takes precedence", () async { 70 | await d 71 | .file("global_test.yaml", jsonEncode({"verbose_trace": true})) 72 | .create(); 73 | 74 | await d 75 | .file("dart_test.yaml", jsonEncode({"verbose_trace": false})) 76 | .create(); 77 | 78 | await d.file("test.dart", """ 79 | import 'package:test/test.dart'; 80 | 81 | void main() { 82 | test("failure", () => throw "oh no"); 83 | } 84 | """).create(); 85 | 86 | var test = await runTest(["test.dart"], 87 | environment: {"DART_TEST_CONFIG": "global_test.yaml"}); 88 | expect(test.stdout, neverEmits(contains("dart:isolate-patch"))); 89 | await test.shouldExit(1); 90 | }); 91 | 92 | group("disallows local-only configuration:", () { 93 | for (var field in [ 94 | "skip", "retry", "test_on", "paths", "filename", "names", "tags", // 95 | "plain_names", "include_tags", "exclude_tags", "pub_serve", "add_tags", 96 | "define_platforms" 97 | ]) { 98 | test("for $field", () async { 99 | await d.file("global_test.yaml", jsonEncode({field: null})).create(); 100 | 101 | await d.file("test.dart", """ 102 | import 'package:test/test.dart'; 103 | 104 | void main() { 105 | test("success", () {}); 106 | } 107 | """).create(); 108 | 109 | var test = await runTest(["test.dart"], 110 | environment: {"DART_TEST_CONFIG": "global_test.yaml"}); 111 | expect( 112 | test.stderr, 113 | containsInOrder( 114 | ["of global_test.yaml: $field isn't supported here.", "^^"])); 115 | await test.shouldExit(exit_codes.data); 116 | }); 117 | } 118 | }); 119 | } 120 | -------------------------------------------------------------------------------- /lib/src/frontend/throws_matcher.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:matcher/matcher.dart'; 8 | 9 | import '../utils.dart'; 10 | import 'async_matcher.dart'; 11 | import 'format_stack_trace.dart'; 12 | 13 | /// This function is deprecated. 14 | /// 15 | /// Use [throwsA] instead. We strongly recommend that you add assertions about 16 | /// at least the type of the error, but you can write `throwsA(anything)` to 17 | /// mimic the behavior of this matcher. 18 | @Deprecated("Will be removed in 0.13.0") 19 | // ignore: deprecated_member_use 20 | const Matcher throws = const Throws(); 21 | 22 | /// This can be used to match three kinds of objects: 23 | /// 24 | /// * A [Function] that throws an exception when called. The function cannot 25 | /// take any arguments. If you want to test that a function expecting 26 | /// arguments throws, wrap it in another zero-argument function that calls 27 | /// the one you want to test. 28 | /// 29 | /// * A [Future] that completes with an exception. Note that this creates an 30 | /// asynchronous expectation. The call to `expect()` that includes this will 31 | /// return immediately and execution will continue. Later, when the future 32 | /// completes, the actual expectation will run. 33 | /// 34 | /// * A [Function] that returns a [Future] that completes with an exception. 35 | /// 36 | /// In all three cases, when an exception is thrown, this will test that the 37 | /// exception object matches [matcher]. If [matcher] is not an instance of 38 | /// [Matcher], it will implicitly be treated as `equals(matcher)`. 39 | Matcher throwsA(matcher) => 40 | // ignore: deprecated_member_use 41 | new Throws(wrapMatcher(matcher)); 42 | 43 | /// Use the [throwsA] function instead. 44 | @Deprecated("Will be removed in 0.13.0") 45 | class Throws extends AsyncMatcher { 46 | final Matcher _matcher; 47 | 48 | const Throws([Matcher matcher]) : this._matcher = matcher; 49 | 50 | // Avoid async/await so we synchronously fail if we match a synchronous 51 | // function. 52 | /*FutureOr*/ matchAsync(item) { 53 | if (item is! Function && item is! Future) { 54 | return "was not a Function or Future"; 55 | } 56 | 57 | if (item is Future) { 58 | return item.then((value) => indent(prettyPrint(value), first: 'emitted '), 59 | onError: _check); 60 | } 61 | 62 | try { 63 | var value = item(); 64 | if (value is Future) { 65 | return value.then( 66 | (value) => indent(prettyPrint(value), 67 | first: 'returned a Future that emitted '), 68 | onError: _check); 69 | } 70 | 71 | return indent(prettyPrint(value), first: 'returned '); 72 | } catch (error, trace) { 73 | return _check(error, trace); 74 | } 75 | } 76 | 77 | Description describe(Description description) { 78 | if (_matcher == null) { 79 | return description.add("throws"); 80 | } else { 81 | return description.add('throws ').addDescriptionOf(_matcher); 82 | } 83 | } 84 | 85 | /// Verifies that [error] matches [_matcher] and returns a [String] 86 | /// description of the failure if it doesn't. 87 | String _check(error, StackTrace trace) { 88 | if (_matcher == null) return null; 89 | 90 | var matchState = {}; 91 | if (_matcher.matches(error, matchState)) return null; 92 | 93 | var result = _matcher 94 | .describeMismatch(error, new StringDescription(), matchState, false) 95 | .toString(); 96 | 97 | var buffer = new StringBuffer(); 98 | buffer.writeln(indent(prettyPrint(error), first: 'threw ')); 99 | if (trace != null) { 100 | buffer 101 | .writeln(indent(formatStackTrace(trace).toString(), first: 'stack ')); 102 | } 103 | if (result.isNotEmpty) buffer.writeln(indent(result, first: 'which ')); 104 | return buffer.toString().trimRight(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /lib/src/util/remote_exception.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:isolate'; 7 | 8 | import 'package:stack_trace/stack_trace.dart'; 9 | 10 | import '../frontend/expect.dart'; 11 | 12 | /// An exception that was thrown remotely. 13 | /// 14 | /// This could be an exception thrown in a different isolate, a different 15 | /// process, or on an entirely different computer. 16 | class RemoteException implements Exception { 17 | /// The original exception's message, if it had one. 18 | /// 19 | /// If the original exception was a plain string, this will contain that 20 | /// string. 21 | final String message; 22 | 23 | /// The value of the original exception's `runtimeType.toString()`. 24 | final String type; 25 | 26 | /// The value of the original exception's `toString()`. 27 | final String _toString; 28 | 29 | /// Serializes [error] and [stackTrace] into a JSON-safe object. 30 | /// 31 | /// Other than JSON- and isolate-safety, no guarantees are made about the 32 | /// serialized format. 33 | static serialize(error, StackTrace stackTrace) { 34 | String message; 35 | if (error is String) { 36 | message = error; 37 | } else { 38 | try { 39 | message = error.message.toString(); 40 | } on NoSuchMethodError catch (_) { 41 | // Do nothing. 42 | } 43 | } 44 | 45 | // It's possible (although unlikely) for a user-defined class to have 46 | // multiple of these supertypes. That's fine, though, since we only care 47 | // about core-library-raised IsolateSpawnExceptions anyway. 48 | String supertype; 49 | if (error is TestFailure) { 50 | supertype = 'TestFailure'; 51 | } else if (error is IsolateSpawnException) { 52 | supertype = 'IsolateSpawnException'; 53 | } 54 | 55 | return { 56 | 'message': message, 57 | 'type': error.runtimeType.toString(), 58 | 'supertype': supertype, 59 | 'toString': error.toString(), 60 | 'stackChain': new Chain.forTrace(stackTrace).toString() 61 | }; 62 | } 63 | 64 | /// Deserializes an exception serialized with [RemoteException.serialize]. 65 | /// 66 | /// The returned [AsyncError] is guaranteed to have a [RemoteException] as its 67 | /// error and a [Chain] as its stack trace. 68 | static AsyncError deserialize(serialized) { 69 | return new AsyncError(_deserializeException(serialized), 70 | new Chain.parse(serialized['stackChain'] as String)); 71 | } 72 | 73 | /// Deserializes the exception portion of [serialized]. 74 | static RemoteException _deserializeException(serialized) { 75 | String message = serialized['message']; 76 | String type = serialized['type']; 77 | String toString = serialized['toString']; 78 | 79 | switch (serialized['supertype'] as String) { 80 | case 'TestFailure': 81 | return new _RemoteTestFailure(message, type, toString); 82 | case 'IsolateSpawnException': 83 | return new _RemoteIsolateSpawnException(message, type, toString); 84 | default: 85 | return new RemoteException._(message, type, toString); 86 | } 87 | } 88 | 89 | RemoteException._(this.message, this.type, this._toString); 90 | 91 | String toString() => _toString; 92 | } 93 | 94 | /// A subclass of [RemoteException] that implements [TestFailure]. 95 | /// 96 | /// It's important to preserve [TestFailure]-ness, because tests have different 97 | /// results depending on whether an exception was a failure or an error. 98 | class _RemoteTestFailure extends RemoteException implements TestFailure { 99 | _RemoteTestFailure(String message, String type, String toString) 100 | : super._(message, type, toString); 101 | } 102 | 103 | /// A subclass of [RemoteException] that implements [IsolateSpawnException]. 104 | class _RemoteIsolateSpawnException extends RemoteException 105 | implements IsolateSpawnException { 106 | _RemoteIsolateSpawnException(String message, String type, String toString) 107 | : super._(message, type, toString); 108 | } 109 | -------------------------------------------------------------------------------- /lib/src/runner/runner_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:stack_trace/stack_trace.dart'; 6 | import 'package:stream_channel/stream_channel.dart'; 7 | 8 | import '../backend/group.dart'; 9 | import '../backend/live_test.dart'; 10 | import '../backend/live_test_controller.dart'; 11 | import '../backend/message.dart'; 12 | import '../backend/metadata.dart'; 13 | import '../backend/state.dart'; 14 | import '../backend/suite.dart'; 15 | import '../backend/suite_platform.dart'; 16 | import '../backend/test.dart'; 17 | import '../util/remote_exception.dart'; 18 | import '../utils.dart'; 19 | import 'spawn_hybrid.dart'; 20 | 21 | /// A test running remotely, controlled by a stream channel. 22 | class RunnerTest extends Test { 23 | final String name; 24 | final Metadata metadata; 25 | final Trace trace; 26 | 27 | /// The channel used to communicate with the test's [IframeListener]. 28 | final MultiChannel _channel; 29 | 30 | RunnerTest(this.name, this.metadata, this.trace, this._channel); 31 | 32 | RunnerTest._(this.name, this.metadata, this.trace, this._channel); 33 | 34 | LiveTest load(Suite suite, {Iterable groups}) { 35 | LiveTestController controller; 36 | VirtualChannel testChannel; 37 | controller = new LiveTestController(suite, this, () { 38 | controller.setState(const State(Status.running, Result.success)); 39 | 40 | testChannel = _channel.virtualChannel(); 41 | _channel.sink.add({'command': 'run', 'channel': testChannel.id}); 42 | 43 | testChannel.stream.listen((message) { 44 | switch (message['type'] as String) { 45 | case 'error': 46 | var asyncError = RemoteException.deserialize(message['error']); 47 | var stackTrace = asyncError.stackTrace; 48 | controller.addError(asyncError.error, stackTrace); 49 | break; 50 | 51 | case 'state-change': 52 | controller.setState(new State( 53 | new Status.parse(message['status'] as String), 54 | new Result.parse(message['result'] as String))); 55 | break; 56 | 57 | case 'message': 58 | controller.message(new Message( 59 | new MessageType.parse(message['message-type'] as String), 60 | message['text'] as String)); 61 | break; 62 | 63 | case 'complete': 64 | controller.completer.complete(); 65 | break; 66 | 67 | case 'spawn-hybrid-uri': 68 | // When we kill the isolate that the test lives in, that will close 69 | // this virtual channel and cause the spawned isolate to close as 70 | // well. 71 | spawnHybridUri(message['url'] as String, message['message']) 72 | .pipe(testChannel.virtualChannel(message['channel'] as int)); 73 | break; 74 | } 75 | }, onDone: () { 76 | // When the test channel closes—presumably becuase the browser 77 | // closed—mark the test as complete no matter what. 78 | if (controller.completer.isCompleted) return; 79 | controller.completer.complete(); 80 | }); 81 | }, () { 82 | // If the test has finished running, just disconnect the channel. 83 | if (controller.completer.isCompleted) { 84 | testChannel.sink.close(); 85 | return; 86 | } 87 | 88 | invoke(() async { 89 | // If the test is still running, send it a message telling it to shut 90 | // down ASAP. This causes the [Invoker] to eagerly throw exceptions 91 | // whenever the test touches it. 92 | testChannel.sink.add({'command': 'close'}); 93 | await controller.completer.future; 94 | testChannel.sink.close(); 95 | }); 96 | }, groups: groups); 97 | return controller.liveTest; 98 | } 99 | 100 | Test forPlatform(SuitePlatform platform) { 101 | if (!metadata.testOn.evaluate(platform)) return null; 102 | return new RunnerTest._( 103 | name, metadata.forPlatform(platform), trace, _channel); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/src/frontend/future_matchers.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:matcher/matcher.dart'; 8 | 9 | import '../utils.dart'; 10 | import 'async_matcher.dart'; 11 | import 'expect.dart'; 12 | import 'utils.dart'; 13 | 14 | /// Matches a [Future] that completes successfully with a value. 15 | /// 16 | /// Note that this creates an asynchronous expectation. The call to `expect()` 17 | /// that includes this will return immediately and execution will continue. 18 | /// Later, when the future completes, the actual expectation will run. 19 | /// 20 | /// To test that a Future completes with an exception, you can use [throws] and 21 | /// [throwsA]. 22 | /// 23 | /// This returns an [AsyncMatcher], so [expect] won't complete until the matched 24 | /// future does. 25 | final Matcher completes = const _Completes(null); 26 | 27 | /// Matches a [Future] that completes succesfully with a value that matches 28 | /// [matcher]. 29 | /// 30 | /// Note that this creates an asynchronous expectation. The call to 31 | /// `expect()` that includes this will return immediately and execution will 32 | /// continue. Later, when the future completes, the actual expectation will run. 33 | /// 34 | /// To test that a Future completes with an exception, you can use [throws] and 35 | /// [throwsA]. 36 | /// 37 | /// This returns an [AsyncMatcher], so [expect] won't complete until the matched 38 | /// future does. 39 | Matcher completion(matcher, [@deprecated String description]) => 40 | new _Completes(wrapMatcher(matcher)); 41 | 42 | class _Completes extends AsyncMatcher { 43 | final Matcher _matcher; 44 | 45 | const _Completes(this._matcher); 46 | 47 | // Avoid async/await so we synchronously start listening to [item]. 48 | /*FutureOr*/ matchAsync(item) { 49 | if (item is! Future) return "was not a Future"; 50 | 51 | return item.then((value) async { 52 | if (_matcher == null) return null; 53 | 54 | String result; 55 | if (_matcher is AsyncMatcher) { 56 | result = await (_matcher as AsyncMatcher).matchAsync(value) as String; 57 | if (result == null) return null; 58 | } else { 59 | var matchState = {}; 60 | if (_matcher.matches(value, matchState)) return null; 61 | result = _matcher 62 | .describeMismatch(value, new StringDescription(), matchState, false) 63 | .toString(); 64 | } 65 | 66 | var buffer = new StringBuffer(); 67 | buffer.writeln(indent(prettyPrint(value), first: 'emitted ')); 68 | if (result.isNotEmpty) buffer.writeln(indent(result, first: ' which ')); 69 | return buffer.toString().trimRight(); 70 | }); 71 | } 72 | 73 | Description describe(Description description) { 74 | if (_matcher == null) { 75 | description.add('completes successfully'); 76 | } else { 77 | description.add('completes to a value that ').addDescriptionOf(_matcher); 78 | } 79 | return description; 80 | } 81 | } 82 | 83 | /// Matches a [Future] that does not complete. 84 | /// 85 | /// Note that this creates an asynchronous expectation. The call to 86 | /// `expect()` that includes this will return immediately and execution will 87 | /// continue. 88 | final Matcher doesNotComplete = const _DoesNotComplete(); 89 | 90 | class _DoesNotComplete extends Matcher { 91 | const _DoesNotComplete(); 92 | 93 | Description describe(Description description) { 94 | description.add("does not complete"); 95 | return description; 96 | } 97 | 98 | bool matches(item, Map matchState) { 99 | if (item is! Future) return false; 100 | item.then((value) { 101 | fail('Future was not expected to complete but completed with a value of ' 102 | '$value'); 103 | }); 104 | expect(pumpEventQueue(), completes); 105 | return true; 106 | } 107 | 108 | Description describeMismatch( 109 | item, Description description, Map matchState, bool verbose) { 110 | if (item is! Future) return description.add("$item is not a Future"); 111 | return description; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lib/src/backend/platform_selector.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:boolean_selector/boolean_selector.dart'; 6 | import 'package:source_span/source_span.dart'; 7 | 8 | import 'operating_system.dart'; 9 | import 'runtime.dart'; 10 | import 'suite_platform.dart'; 11 | 12 | /// The set of variable names that are valid for all platform selectors. 13 | final _universalValidVariables = new Set.from( 14 | ["posix", "dart-vm", "browser", "js", "blink", "google"]) 15 | ..addAll(Runtime.builtIn.map((runtime) => runtime.identifier)) 16 | ..addAll(OperatingSystem.all.map((os) => os.identifier)); 17 | 18 | /// An expression for selecting certain platforms, including operating systems 19 | /// and browsers. 20 | /// 21 | /// This uses the [boolean selector][] syntax. 22 | /// 23 | /// [boolean selector]: https://pub.dartlang.org/packages/boolean_selector 24 | class PlatformSelector { 25 | /// A selector that declares that a test can be run on all platforms. 26 | static const all = const PlatformSelector._(BooleanSelector.all); 27 | 28 | /// The boolean selector used to implement this selector. 29 | final BooleanSelector _inner; 30 | 31 | /// The source span from which this selector was parsed. 32 | final SourceSpan _span; 33 | 34 | /// Parses [selector]. 35 | /// 36 | /// If [span] is passed, it indicates the location of the text for [selector] 37 | /// in a larger document. It's used for error reporting. 38 | PlatformSelector.parse(String selector, [SourceSpan span]) 39 | : _inner = _wrapFormatException( 40 | () => new BooleanSelector.parse(selector), span), 41 | _span = span; 42 | 43 | const PlatformSelector._(this._inner) : _span = null; 44 | 45 | /// Runs [body] and wraps any [FormatException] it throws in a 46 | /// [SourceSpanFormatException] using [span]. 47 | /// 48 | /// If [span] is `null`, runs [body] as-is. 49 | static T _wrapFormatException(T body(), SourceSpan span) { 50 | if (span == null) return body(); 51 | 52 | try { 53 | return body(); 54 | } on FormatException catch (error) { 55 | throw new SourceSpanFormatException(error.message, span); 56 | } 57 | } 58 | 59 | /// Throws a [FormatException] if this selector uses any variables that don't 60 | /// appear either in [validVariables] or in the set of variables that are 61 | /// known to be valid for all selectors. 62 | void validate(Set validVariables) { 63 | if (identical(this, all)) return; 64 | 65 | _wrapFormatException( 66 | () => _inner.validate((name) => 67 | _universalValidVariables.contains(name) || 68 | validVariables.contains(name)), 69 | _span); 70 | } 71 | 72 | /// Returns whether the selector matches the given [platform]. 73 | /// 74 | /// [os] defaults to [OperatingSystem.none]. 75 | bool evaluate(SuitePlatform platform) { 76 | return _inner.evaluate((String variable) { 77 | if (variable == platform.runtime.identifier) return true; 78 | if (variable == platform.runtime.parent?.identifier) return true; 79 | if (variable == platform.os.identifier) return true; 80 | switch (variable) { 81 | case "dart-vm": 82 | return platform.runtime.isDartVM; 83 | case "browser": 84 | return platform.runtime.isBrowser; 85 | case "js": 86 | return platform.runtime.isJS; 87 | case "blink": 88 | return platform.runtime.isBlink; 89 | case "posix": 90 | return platform.os.isPosix; 91 | case "google": 92 | return platform.inGoogle; 93 | default: 94 | return false; 95 | } 96 | }); 97 | } 98 | 99 | /// Returns a new [PlatformSelector] that matches only platforms matched by 100 | /// both [this] and [other]. 101 | PlatformSelector intersection(PlatformSelector other) { 102 | if (other == PlatformSelector.all) return this; 103 | return new PlatformSelector._(_inner.intersection(other._inner)); 104 | } 105 | 106 | String toString() => _inner.toString(); 107 | 108 | bool operator ==(other) => 109 | other is PlatformSelector && _inner == other._inner; 110 | 111 | int get hashCode => _inner.hashCode; 112 | } 113 | -------------------------------------------------------------------------------- /lib/src/backend/state.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// The state of a [LiveTest]. 6 | /// 7 | /// A test's state is made up of two components, its [status] and its [result]. 8 | /// The [status] represents where the test is in its process of running; the 9 | /// [result] represents the outcome as far as its known. 10 | class State { 11 | /// Where the test is in its process of running. 12 | final Status status; 13 | 14 | /// The outcome of the test, as far as it's known. 15 | /// 16 | /// Note that if [status] is [Status.pending], [result] will always be 17 | /// [Result.success] since the test hasn't yet had a chance to fail. 18 | final Result result; 19 | 20 | /// Whether a test in this state is expected to be done running code. 21 | /// 22 | /// If [status] is [Status.complete] and [result] doesn't indicate an error, a 23 | /// properly-written test case should not be running any more code. However, 24 | /// it may have started asynchronous processes without notifying the test 25 | /// runner. 26 | bool get shouldBeDone => status == Status.complete && result.isPassing; 27 | 28 | const State(this.status, this.result); 29 | 30 | bool operator ==(other) => 31 | other is State && status == other.status && result == other.result; 32 | 33 | int get hashCode => status.hashCode ^ (7 * result.hashCode); 34 | 35 | String toString() { 36 | if (status == Status.pending) return "pending"; 37 | if (status == Status.complete) return result.toString(); 38 | if (result == Result.success) return "running"; 39 | return "running with $result"; 40 | } 41 | } 42 | 43 | /// Where the test is in its process of running. 44 | class Status { 45 | /// The test has not yet begun running. 46 | static const pending = const Status._("pending"); 47 | 48 | /// The test is currently running. 49 | static const running = const Status._("running"); 50 | 51 | /// The test has finished running. 52 | /// 53 | /// Note that even if the test is marked [complete], it may still be running 54 | /// code asynchronously. A test is considered complete either once it hits its 55 | /// first error or when all [expectAsync] callbacks have been called and any 56 | /// returned [Future] has completed, but it's possible for further processing 57 | /// to happen, which may cause further errors. 58 | static const complete = const Status._("complete"); 59 | 60 | /// The name of the status. 61 | final String name; 62 | 63 | factory Status.parse(String name) { 64 | switch (name) { 65 | case "pending": 66 | return Status.pending; 67 | case "running": 68 | return Status.running; 69 | case "complete": 70 | return Status.complete; 71 | default: 72 | throw new ArgumentError('Invalid status name "$name".'); 73 | } 74 | } 75 | 76 | const Status._(this.name); 77 | 78 | String toString() => name; 79 | } 80 | 81 | /// The outcome of the test, as far as it's known. 82 | class Result { 83 | /// The test has not yet failed in any way. 84 | /// 85 | /// Note that this doesn't mean that the test won't fail in the future. 86 | static const success = const Result._("success"); 87 | 88 | /// The test, or some part of it, has been skipped. 89 | /// 90 | /// This implies that the test hasn't failed *yet*. However, it this doesn't 91 | /// mean that the test won't fail in the future. 92 | static const skipped = const Result._("skipped"); 93 | 94 | /// The test has failed. 95 | /// 96 | /// A failure is specifically caused by a [TestFailure] being thrown; any 97 | /// other exception causes an error. 98 | static const failure = const Result._("failure"); 99 | 100 | /// The test has crashed. 101 | /// 102 | /// Any exception other than a [TestFailure] is considered to be an error. 103 | static const error = const Result._("error"); 104 | 105 | /// The name of the result. 106 | final String name; 107 | 108 | /// Whether this is a passing result. 109 | /// 110 | /// A test is considered to have passed if it's a success or if it was 111 | /// skipped. 112 | bool get isPassing => this == success || this == skipped; 113 | 114 | /// Whether this is a failing result. 115 | /// 116 | /// A test is considered to have failed if it experiences a failure or an 117 | /// error. 118 | bool get isFailing => !isPassing; 119 | 120 | factory Result.parse(String name) { 121 | switch (name) { 122 | case "success": 123 | return Result.success; 124 | case "skipped": 125 | return Result.skipped; 126 | case "failure": 127 | return Result.failure; 128 | case "error": 129 | return Result.error; 130 | default: 131 | throw new ArgumentError('Invalid result name "$name".'); 132 | } 133 | } 134 | 135 | const Result._(this.name); 136 | 137 | String toString() => name; 138 | } 139 | -------------------------------------------------------------------------------- /test/io.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:io'; 7 | 8 | import 'package:package_resolver/package_resolver.dart'; 9 | import 'package:path/path.dart' as p; 10 | import 'package:test_descriptor/test_descriptor.dart' as d; 11 | import 'package:test_process/test_process.dart'; 12 | 13 | import 'package:test/test.dart'; 14 | 15 | /// The path to the root directory of the `test` package. 16 | final Future packageDir = PackageResolver.current.packagePath('test'); 17 | 18 | /// The path to the `pub` executable in the current Dart SDK. 19 | final _pubPath = p.absolute(p.join(p.dirname(Platform.resolvedExecutable), 20 | Platform.isWindows ? 'pub.bat' : 'pub')); 21 | 22 | /// The platform-specific message emitted when a nonexistent file is loaded. 23 | final String noSuchFileMessage = Platform.isWindows 24 | ? "The system cannot find the file specified." 25 | : "No such file or directory"; 26 | 27 | /// A regular expression that matches the output of "pub serve". 28 | final _servingRegExp = 29 | new RegExp(r'^Serving myapp [a-z]+ on http://localhost:(\d+)$'); 30 | 31 | /// An operating system name that's different than the current operating system. 32 | final otherOS = Platform.isWindows ? "mac-os" : "windows"; 33 | 34 | /// The port of a pub serve instance run via [runPubServe]. 35 | /// 36 | /// This is only set after [runPubServe] is called. 37 | int get pubServePort => _pubServePort; 38 | int _pubServePort; 39 | 40 | /// Expects that the entire stdout stream of [test] equals [expected]. 41 | void expectStdoutEquals(TestProcess test, String expected) => 42 | _expectStreamEquals(test.stdoutStream(), expected); 43 | 44 | /// Expects that the entire stderr stream of [test] equals [expected]. 45 | void expectStderrEquals(TestProcess test, String expected) => 46 | _expectStreamEquals(test.stderrStream(), expected); 47 | 48 | /// Expects that the entirety of the line stream [stream] equals [expected]. 49 | void _expectStreamEquals(Stream stream, String expected) { 50 | expect((() async { 51 | var lines = await stream.toList(); 52 | expect(lines.join("\n").trim(), equals(expected.trim())); 53 | })(), completes); 54 | } 55 | 56 | /// Returns a [StreamMatcher] that asserts that the stream emits strings 57 | /// containing each string in [strings] in order. 58 | /// 59 | /// This expects each string in [strings] to match a different string in the 60 | /// stream. 61 | StreamMatcher containsInOrder(Iterable strings) => 62 | emitsInOrder(strings.map((string) => emitsThrough(contains(string)))); 63 | 64 | /// Runs the test executable with the package root set properly. 65 | Future runTest(Iterable args, 66 | {String reporter, 67 | int concurrency, 68 | Map environment, 69 | bool forwardStdio = false}) async { 70 | concurrency ??= 1; 71 | 72 | var allArgs = [ 73 | p.absolute(p.join(await packageDir, 'bin/test.dart')), 74 | "--concurrency=$concurrency" 75 | ]; 76 | if (reporter != null) allArgs.add("--reporter=$reporter"); 77 | allArgs.addAll(args); 78 | 79 | if (environment == null) environment = {}; 80 | environment.putIfAbsent("_DART_TEST_TESTING", () => "true"); 81 | 82 | return await runDart(allArgs, 83 | environment: environment, 84 | description: "dart bin/test.dart", 85 | forwardStdio: forwardStdio); 86 | } 87 | 88 | /// Runs Dart. 89 | Future runDart(Iterable args, 90 | {Map environment, 91 | String description, 92 | bool forwardStdio = false}) async { 93 | var allArgs = [] 94 | ..addAll(Platform.executableArguments.where((arg) => 95 | !arg.startsWith("--package-root=") && !arg.startsWith("--packages="))) 96 | ..add(await PackageResolver.current.processArgument) 97 | ..addAll(args); 98 | 99 | return await TestProcess.start( 100 | p.absolute(Platform.resolvedExecutable), allArgs, 101 | workingDirectory: d.sandbox, 102 | environment: environment, 103 | description: description, 104 | forwardStdio: forwardStdio); 105 | } 106 | 107 | /// Runs Pub. 108 | Future runPub(Iterable args, 109 | {Map environment}) { 110 | return TestProcess.start(_pubPath, args, 111 | workingDirectory: d.sandbox, 112 | environment: environment, 113 | description: "pub ${args.first}"); 114 | } 115 | 116 | /// Runs "pub serve". 117 | /// 118 | /// This returns assigns [_pubServePort] to a future that will complete to the 119 | /// port of the "pub serve" instance. 120 | Future runPubServe( 121 | {Iterable args, 122 | String workingDirectory, 123 | Map environment}) async { 124 | var allArgs = ['serve', '--port', '0']; 125 | if (args != null) allArgs.addAll(args); 126 | 127 | var pub = await runPub(allArgs, environment: environment); 128 | 129 | Match match; 130 | while (match == null) { 131 | match = _servingRegExp.firstMatch(await pub.stdout.next); 132 | } 133 | _pubServePort = int.parse(match[1]); 134 | 135 | return pub; 136 | } 137 | -------------------------------------------------------------------------------- /lib/src/runner/compiler_pool.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:convert'; 7 | import 'dart:io'; 8 | 9 | import 'package:async/async.dart'; 10 | import 'package:package_resolver/package_resolver.dart'; 11 | import 'package:path/path.dart' as p; 12 | import 'package:pool/pool.dart'; 13 | 14 | import '../util/io.dart'; 15 | import 'configuration.dart'; 16 | import 'configuration/suite.dart'; 17 | 18 | /// A regular expression matching the first status line printed by dart2js. 19 | final _dart2jsStatus = 20 | new RegExp(r"^Dart file \(.*\) compiled to JavaScript: .*\n?"); 21 | 22 | /// A pool of `dart2js` instances. 23 | /// 24 | /// This limits the number of compiler instances running concurrently. 25 | class CompilerPool { 26 | /// The test runner configuration. 27 | final _config = Configuration.current; 28 | 29 | /// The internal pool that controls the number of process running at once. 30 | final Pool _pool; 31 | 32 | /// The currently-active dart2js processes. 33 | final _processes = new Set(); 34 | 35 | /// Whether [close] has been called. 36 | bool get _closed => _closeMemo.hasRun; 37 | 38 | /// The memoizer for running [close] exactly once. 39 | final _closeMemo = new AsyncMemoizer(); 40 | 41 | /// Extra arguments to pass to dart2js. 42 | final List _extraArgs; 43 | 44 | /// Creates a compiler pool that multiple instances of `dart2js` at once. 45 | CompilerPool([Iterable extraArgs]) 46 | : _pool = new Pool(Configuration.current.concurrency), 47 | _extraArgs = extraArgs?.toList() ?? const []; 48 | 49 | /// Compiles [code] to [jsPath]. 50 | /// 51 | /// This wraps the Dart code in the standard browser-testing wrapper. 52 | /// 53 | /// The returned [Future] will complete once the `dart2js` process completes 54 | /// *and* all its output has been printed to the command line. 55 | Future compile(String code, String jsPath, SuiteConfiguration suiteConfig) { 56 | return _pool.withResource(() { 57 | if (_closed) return null; 58 | 59 | return withTempDir((dir) async { 60 | var wrapperPath = p.join(dir, "runInBrowser.dart"); 61 | new File(wrapperPath).writeAsStringSync(code); 62 | 63 | var dart2jsPath = _config.dart2jsPath; 64 | if (Platform.isWindows) dart2jsPath += '.bat'; 65 | 66 | var args = [ 67 | "--enable-asserts", 68 | wrapperPath, 69 | "--out=$jsPath", 70 | await PackageResolver.current.processArgument 71 | ] 72 | ..addAll(_extraArgs) 73 | ..addAll(suiteConfig.dart2jsArgs); 74 | 75 | if (_config.color) args.add("--enable-diagnostic-colors"); 76 | 77 | var process = await Process.start(dart2jsPath, args); 78 | if (_closed) { 79 | process.kill(); 80 | return; 81 | } 82 | 83 | _processes.add(process); 84 | 85 | /// Wait until the process is entirely done to print out any output. 86 | /// This can produce a little extra time for users to wait with no 87 | /// update, but it also avoids some really nasty-looking interleaved 88 | /// output. Write both stdout and stderr to the same buffer in case 89 | /// they're intended to be printed in order. 90 | var buffer = new StringBuffer(); 91 | 92 | await Future.wait([ 93 | process.stdout.transform(utf8.decoder).forEach(buffer.write), 94 | process.stderr.transform(utf8.decoder).forEach(buffer.write), 95 | ]); 96 | 97 | var exitCode = await process.exitCode; 98 | _processes.remove(process); 99 | if (_closed) return; 100 | 101 | var output = buffer.toString().replaceFirst(_dart2jsStatus, ''); 102 | if (output.isNotEmpty) print(output); 103 | 104 | if (exitCode != 0) throw "dart2js failed."; 105 | 106 | _fixSourceMap(jsPath + '.map'); 107 | }); 108 | }); 109 | } 110 | 111 | // TODO(nweiz): Remove this when sdk#17544 is fixed. 112 | /// Fix up the source map at [mapPath] so that it points to absolute file: 113 | /// URIs that are resolvable by the browser. 114 | void _fixSourceMap(String mapPath) { 115 | var map = jsonDecode(new File(mapPath).readAsStringSync()); 116 | var root = map['sourceRoot'] as String; 117 | 118 | map['sources'] = map['sources'].map((source) { 119 | var url = Uri.parse(root + '$source'); 120 | if (url.scheme != '' && url.scheme != 'file') return source; 121 | if (url.path.endsWith("/runInBrowser.dart")) return ""; 122 | return p.toUri(mapPath).resolveUri(url).toString(); 123 | }).toList(); 124 | 125 | new File(mapPath).writeAsStringSync(jsonEncode(map)); 126 | } 127 | 128 | /// Closes the compiler pool. 129 | /// 130 | /// This kills all currently-running compilers and ensures that no more will 131 | /// be started. It returns a [Future] that completes once all the compilers 132 | /// have been killed and all resources released. 133 | Future close() { 134 | return _closeMemo.runOnce(() async { 135 | await Future.wait(_processes.map((process) async { 136 | process.kill(); 137 | await process.exitCode; 138 | })); 139 | }); 140 | } 141 | } 142 | --------------------------------------------------------------------------------