diff --git a/dart_test.yaml b/dart_test.yaml index 137119b38d6abd56186134fb33d5544873ce7bad..d8024bedffc05bdb2f2a1356646949ab3f287ad9 100644 --- a/dart_test.yaml +++ b/dart_test.yaml @@ -20,3 +20,7 @@ tags: ie: add_tags: [dart2js] test_on: windows + + # Tests that run pub. These tests may need to be excluded when there are local + # dependency_overrides. + pub: diff --git a/lib/src/backend/invoker.dart b/lib/src/backend/invoker.dart index 0a0a31846fb34e740fac30b4687b26379dd9cff7..b93a801439004ff3ed0a31e5d4cbda19d77fb479 100644 --- a/lib/src/backend/invoker.dart +++ b/lib/src/backend/invoker.dart @@ -279,6 +279,9 @@ class Invoker { // handled, we can end up with [onError] callbacks firing before the // corresponding [onStateChange], which violates the timing // guarantees. + // + // Using [new Future] also avoids starving the DOM or other + // microtask-level events. new Future(_test._body) .then((_) => removeOutstandingCallback()); @@ -288,9 +291,7 @@ class Invoker { _controller.setState( new State(Status.complete, liveTest.state.result)); - // Use [Timer.run] here to avoid starving the DOM or other - // non-microtask events. - Timer.run(_controller.completer.complete); + _controller.completer.complete(); }, zoneValues: { #test.invoker: this, // Use the invoker as a key so that multiple invokers can have different diff --git a/lib/src/runner/engine.dart b/lib/src/runner/engine.dart index ca47c0998801c94ae39a7d58dacbeb066b6f033b..62a6db5b2f94ab3124b27f3c08e1c2f1fa0b5347 100644 --- a/lib/src/runner/engine.dart +++ b/lib/src/runner/engine.dart @@ -16,6 +16,9 @@ import '../backend/live_test.dart'; import '../backend/live_test_controller.dart'; import '../backend/state.dart'; import '../backend/test.dart'; +import '../util/iterable_set.dart'; +import 'live_suite.dart'; +import 'live_suite_controller.dart'; import 'load_suite.dart'; import 'runner_suite.dart'; @@ -102,8 +105,8 @@ class Engine { Set<RunnerSuite> get addedSuites => new UnmodifiableSetView(_addedSuites); final _addedSuites = new Set<RunnerSuite>(); - /// A broadcast that emits each [RunnerSuite] as it's added to the engine via - /// [suiteSink]. + /// A broadcast stream that emits each [RunnerSuite] as it's added to the + /// engine via [suiteSink]. /// /// Note that if a [LoadSuite] is added, this will only return that suite, not /// the suite it loads. @@ -112,7 +115,26 @@ class Engine { Stream<RunnerSuite> get onSuiteAdded => _onSuiteAddedController.stream; final _onSuiteAddedController = new StreamController<RunnerSuite>.broadcast(); - /// All the currently-known tests that have run, are running, or will run. + /// All the currently-known suites that have run or are running. + /// + /// These are [LiveSuite]s, representing the in-progress state of each suite + /// as its component tests are being run. + /// + /// Note that unlike [addedSuites], for suites that are loaded using + /// [LoadSuite]s, both the [LoadSuite] and the suite it loads will eventually + /// be in this set. + Set<LiveSuite> get liveSuites => new UnmodifiableSetView(_liveSuites); + final _liveSuites = new Set<LiveSuite>(); + + /// A broadcast stream that emits each [LiveSuite] as it's loaded. + /// + /// Note that unlike [onSuiteAdded], for suites that are loaded using + /// [LoadSuite]s, both the [LoadSuite] and the suite it loads will eventually + /// be emitted by this stream. + Stream<LiveSuite> get onSuiteStarted => _onSuiteStartedController.stream; + final _onSuiteStartedController = new StreamController<LiveSuite>.broadcast(); + + /// All the currently-known tests that have run or are running. /// /// These are [LiveTest]s, representing the in-progress state of each test. /// Tests that have not yet begun running are marked [Status.pending]; tests @@ -122,26 +144,27 @@ class Engine { /// [skipped], [failed], and [active]. /// /// [LiveTest.run] must not be called on these tests. - List<LiveTest> get liveTests => new UnmodifiableListView(_liveTests); - final _liveTests = new List<LiveTest>(); + Set<LiveTest> get liveTests => new UnionSet.from( + [passed, skipped, failed, new IterableSet(active)], + disjoint: true); /// A stream that emits each [LiveTest] as it's about to start running. /// /// This is guaranteed to fire before [LiveTest.onStateChange] first fires. - Stream<LiveTest> get onTestStarted => _onTestStartedController.stream; - final _onTestStartedController = new StreamController<LiveTest>.broadcast(); + Stream<LiveTest> get onTestStarted => _onTestStartedGroup.stream; + final _onTestStartedGroup = new StreamGroup<LiveTest>.broadcast(); /// The set of tests that have completed and been marked as passing. - Set<LiveTest> get passed => new UnmodifiableSetView(_passed); - final _passed = new Set<LiveTest>(); + Set<LiveTest> get passed => _passedGroup.set; + final _passedGroup = new UnionSetController<LiveTest>(disjoint: true); /// The set of tests that have completed and been marked as skipped. - Set<LiveTest> get skipped => new UnmodifiableSetView(_skipped); - final _skipped = new Set<LiveTest>(); + Set<LiveTest> get skipped => _skippedGroup.set; + final _skippedGroup = new UnionSetController<LiveTest>(disjoint: true); /// The set of tests that have completed and been marked as failing or error. - Set<LiveTest> get failed => new UnmodifiableSetView(_failed); - final _failed = new Set<LiveTest>(); + Set<LiveTest> get failed => _failedGroup.set; + final _failedGroup = new UnionSetController<LiveTest>(disjoint: true); /// The tests that are still running, in the order they begain running. List<LiveTest> get active => new UnmodifiableListView(_active); @@ -177,6 +200,8 @@ class Engine { ? (concurrency == null ? 2 : concurrency * 2) : maxSuites) { _group.future.then((_) { + _onTestStartedGroup.close(); + _onSuiteStartedController.close(); if (_closedBeforeDone == null) _closedBeforeDone = false; }).catchError((_) { // Don't top-level errors. They'll be thrown via [success] anyway. @@ -213,18 +238,23 @@ class Engine { _group.add(new Future.sync(() async { var loadResource = await _loadPool.request(); + var controller; if (suite is LoadSuite) { - suite = await _addLoadSuite(suite); - if (suite == null) { + controller = await _addLoadSuite(suite); + if (controller == null) { loadResource.release(); return; } + } else { + controller = new LiveSuiteController(suite); } + _addLiveSuite(controller.liveSuite); + await _runPool.withResource(() async { if (_closed) return; - await _runGroup(suite, suite.group, []); - loadResource.allowRelease(() => suite.close()); + await _runGroup(controller, controller.liveSuite.suite.group, []); + loadResource.allowRelease(() => controller.close()); }); })); }, onDone: () { @@ -235,23 +265,26 @@ class Engine { return success; } - /// Runs all the entries in [entries] in sequence. + /// Runs all the entries in [group] in sequence. /// + /// [suiteController] is the controller fo the suite that contains [group]. /// [parents] is a list of groups that contain [group]. It may be modified, /// but it's guaranteed to be in its original state once this function has /// finished. - Future _runGroup(RunnerSuite suite, Group group, List<Group> parents) async { + Future _runGroup(LiveSuiteController suiteController, Group group, + List<Group> parents) async { parents.add(group); try { if (group.metadata.skip) { - await _runLiveTest(_skippedTest(suite, group, parents)); + await _runSkippedTest(suiteController, group, parents); return; } var setUpAllSucceeded = true; if (group.setUpAll != null) { - var liveTest = group.setUpAll.load(suite, groups: parents); - await _runLiveTest(liveTest, countSuccess: false); + var liveTest = group.setUpAll.load(suiteController.liveSuite.suite, + groups: parents); + await _runLiveTest(suiteController, liveTest, countSuccess: false); setUpAllSucceeded = liveTest.state.result == Result.success; } @@ -260,12 +293,14 @@ class Engine { if (_closed) return; if (entry is Group) { - await _runGroup(suite, entry, parents); + await _runGroup(suiteController, entry, parents); } else if (entry.metadata.skip) { - await _runLiveTest(_skippedTest(suite, entry, parents)); + await _runSkippedTest(suiteController, entry, parents); } else { var test = entry as Test; - await _runLiveTest(test.load(suite, groups: parents)); + await _runLiveTest( + suiteController, + test.load(suiteController.liveSuite.suite, groups: parents)); } } } @@ -273,8 +308,9 @@ class Engine { // Even if we're closed or setUpAll failed, we want to run all the // teardowns to ensure that any state is properly cleaned up. if (group.tearDownAll != null) { - var liveTest = group.tearDownAll.load(suite, groups: parents); - await _runLiveTest(liveTest, countSuccess: false); + var liveTest = group.tearDownAll.load(suiteController.liveSuite.suite, + groups: parents); + await _runLiveTest(suiteController, liveTest, countSuccess: false); if (_closed) await liveTest.close(); } } finally { @@ -282,37 +318,18 @@ class Engine { } } - /// Returns a dummy [LiveTest] for a test or group marked as "skip". - /// - /// [parents] is a list of groups that contain [entry]. - LiveTest _skippedTest(RunnerSuite suite, GroupEntry entry, - List<Group> parents) { - // The netry name will be `null` for the root group. - var test = new LocalTest(entry.name ?? "(suite)", entry.metadata, () {}); - - var controller; - controller = new LiveTestController(suite, test, () { - controller.setState(const State(Status.running, Result.success)); - controller.setState(const State(Status.complete, Result.success)); - controller.completer.complete(); - }, () {}, groups: parents); - return controller.liveTest; - } - - /// Runs [liveTest]. + /// Runs [liveTest] using [suiteController]. /// /// If [countSuccess] is `true` (the default), the test is put into [passed] /// if it succeeds. Otherwise, it's removed from [liveTests] entirely. - Future _runLiveTest(LiveTest liveTest, {bool countSuccess: true}) async { - _liveTests.add(liveTest); + Future _runLiveTest(LiveSuiteController suiteController, LiveTest liveTest, + {bool countSuccess: true}) async { _active.add(liveTest); // If there were no active non-load tests, the current active test would // have been a load test. In that case, remove it, since now we have a // non-load test to add. - if (_active.isNotEmpty && _active.first.suite is LoadSuite) { - _liveTests.remove(_active.removeFirst()); - } + if (_active.first.suite is LoadSuite) _active.removeFirst(); liveTest.onStateChange.listen((state) { if (state.status != Status.complete) return; @@ -321,35 +338,45 @@ class Engine { // If we're out of non-load tests, surface a load test. if (_active.isEmpty && _activeLoadTests.isNotEmpty) { _active.add(_activeLoadTests.first); - _liveTests.add(_activeLoadTests.first); - } - - if (state.result != Result.success) { - _passed.remove(liveTest); - _failed.add(liveTest); - } else if (liveTest.test.metadata.skip) { - _skipped.add(liveTest); - } else if (countSuccess) { - _passed.add(liveTest); - } else { - _liveTests.remove(liveTest); } }); - _onTestStartedController.add(liveTest); + suiteController.reportLiveTest(liveTest, countSuccess: countSuccess); - // First, schedule a microtask to ensure that [onTestStarted] fires before - // the first [LiveTest.onStateChange] event. Once the test finishes, use - // [new Future] to do a coarse-grained event loop pump to avoid starving - // non-microtask events. + // Schedule a microtask to ensure that [onTestStarted] fires before the + // first [LiveTest.onStateChange] event. await new Future.microtask(liveTest.run); + + // Once the test finishes, use [new Future] to do a coarse-grained event + // loop pump to avoid starving non-microtask events. await new Future(() {}); if (!_restarted.contains(liveTest)) return; - await _runLiveTest(liveTest.copy(), countSuccess: countSuccess); + await _runLiveTest(suiteController, liveTest.copy(), + countSuccess: countSuccess); _restarted.remove(liveTest); } + /// Runs a dummy [LiveTest] for a test or group marked as "skip". + /// + /// [suiteController] is the controller for the suite that contains [entry]. + /// [parents] is a list of groups that contain [entry]. + Future _runSkippedTest(LiveSuiteController suiteController, GroupEntry entry, + List<Group> parents) { + // The netry name will be `null` for the root group. + var test = new LocalTest(entry.name ?? "(suite)", entry.metadata, () {}); + + var controller; + controller = new LiveTestController( + suiteController.liveSuite.suite, test, () { + controller.setState(const State(Status.running, Result.success)); + controller.setState(const State(Status.complete, Result.success)); + controller.completer.complete(); + }, () {}, groups: parents); + + return _runLiveTest(suiteController, controller.liveTest); + } + /// Closes [liveTest] and tells the engine to re-run it once it's done /// running. /// @@ -369,19 +396,18 @@ class Engine { await liveTest.close(); } - /// Adds listeners for [suite]. + /// Runs [suite] and returns the [LiveSuiteController] for the suite it loads. /// - /// Load suites have specific logic apart from normal test suites. - Future<RunnerSuite> _addLoadSuite(LoadSuite suite) async { - var liveTest = await suite.test.load(suite); + /// Returns `null` if the suite fails to load. + Future<LiveSuiteController> _addLoadSuite(LoadSuite suite) async { + var controller = new LiveSuiteController(suite); + _addLiveSuite(controller.liveSuite); + var liveTest = await suite.test.load(suite); _activeLoadTests.add(liveTest); // Only surface the load test if there are no other tests currently running. - if (_active.isEmpty) { - _liveTests.add(liveTest); - _active.add(liveTest); - } + if (_active.isEmpty) _active.add(liveTest); liveTest.onStateChange.listen((state) { if (state.status != Status.complete) return; @@ -392,26 +418,43 @@ class Engine { // load test. if (_active.isNotEmpty && _active.first.suite == suite) { _active.remove(liveTest); - _liveTests.remove(liveTest); - - if (_activeLoadTests.isNotEmpty) { - _active.add(_activeLoadTests.last); - _liveTests.add(_activeLoadTests.last); - } + if (_activeLoadTests.isNotEmpty) _active.add(_activeLoadTests.last); } + }); + + controller.reportLiveTest(liveTest, countSuccess: false); + controller.noMoreLiveTests(); + + // Schedule a microtask to ensure that [onTestStarted] fires before the + // first [LiveTest.onStateChange] event. + new Future.microtask(liveTest.run); - // Surface the load test if it fails so that the user can see the failure. - if (state.result == Result.success) return; - _failed.add(liveTest); - _liveTests.add(liveTest); + var innerSuite = await suite.suite; + if (innerSuite == null) return null; + + var innerController = new LiveSuiteController(innerSuite); + innerController.liveSuite.onClose.then((_) { + // When the main suite is closed, close the load suite and its test as + // well. This doesn't release any resources, but it does close streams + // which indicates that the load test won't experience an error in the + // future. + liveTest.close(); + controller.close(); }); - // Run the test immediately. We don't want loading to be blocked on suites - // that are already running. - _onTestStartedController.add(liveTest); - await liveTest.run(); + return innerController; + } + + /// Add [liveSuite] and the information it exposes to the engine's + /// informational streams and collections. + void _addLiveSuite(LiveSuite liveSuite) { + _liveSuites.add(liveSuite); + _onSuiteStartedController.add(liveSuite); - return suite.suite; + _onTestStartedGroup.add(liveSuite.onTestStarted); + _passedGroup.add(liveSuite.passed); + _skippedGroup.add(liveSuite.skipped); + _failedGroup.add(liveSuite.failed); } /// Signals that the caller is done paying attention to test results and the diff --git a/lib/src/runner/live_suite.dart b/lib/src/runner/live_suite.dart new file mode 100644 index 0000000000000000000000000000000000000000..d15f0da4ec90f1bd7c125b849e4eaad1a9a34d8c --- /dev/null +++ b/lib/src/runner/live_suite.dart @@ -0,0 +1,88 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:collection/collection.dart'; + +import '../backend/live_test.dart'; +import 'runner_suite.dart'; + +/// A view of the execution of a test suite. +/// +/// This is distinct from [Suite] because it represents the progress of running +/// a suite rather than the suite's contents. It provides events and collections +/// that give the caller a view into the suite's current state. +abstract class LiveSuite { + /// The suite that's being run. + RunnerSuite get suite; + + /// Whether the suite has completed. + /// + /// Note that even if this returns `true`, the suite may still be running code + /// asynchronously. A suite is considered complete once all of its tests are + /// complete, but it's possible for a test to continue running even after it's + /// been marked complete—see [LiveTest.isComplete] for details. + /// + /// The [isClosed] getter can be used to determine whether the suite and its + /// tests are guaranteed to emit no more events. + bool get isComplete; + + /// A [Future] that completes once the suite is complete. + /// + /// Note that even once this completes, the suite may still be running code + /// asynchronously. A suite is considered complete once all of its tests are + /// complete, but it's possible for a test to continue running even after it's + /// been marked complete—see [LiveTest.isComplete] for details. + /// + /// The [onComplete] future can be used to determine when the suite and its + /// tests are guaranteed to emit no more events. + Future get onComplete; + + /// Whether the suite has been closed. + /// + /// If this is `true`, no code is running for the suite or any of its tests. + /// At this point, the caller can be sure that the suites' tests are all in + /// fixed states that will not change in the future. + bool get isClosed; + + /// A [Future] that completes when the suite has been closed. + /// + /// Once this completes, no code is running for the suite or any of its tests. + /// At this point, the caller can be sure that the suites' tests are all in + /// fixed states that will not change in the future. + Future get onClose; + + /// All the currently-known tests in this suite that have run or are running. + /// + /// This is guaranteed to contain the same tests as the union of [passed], + /// [skipped], [failed], and [active]. + Set<LiveTest> get liveTests { + var sets = [passed, skipped, failed]; + if (active != null) sets.add(new Set.from([active])); + return new UnionSet.from(sets); + } + + /// A stream that emits each [LiveTest] in this suite as it's about to start + /// running. + /// + /// This is guaranteed to fire before [LiveTest.onStateChange] first fires. It + /// will close once all tests the user has selected are run. + Stream<LiveTest> get onTestStarted; + + /// The set of tests in this suite that have completed and been marked as + /// passing. + Set<LiveTest> get passed; + + /// The set of tests in this suite that have completed and been marked as + /// skipped. + Set<LiveTest> get skipped; + + /// The set of tests in this suite that have completed and been marked as + /// failing or error. + Set<LiveTest> get failed; + + /// The currently running test in this suite, or `null` if no test is running. + LiveTest get active; +} diff --git a/lib/src/runner/live_suite_controller.dart b/lib/src/runner/live_suite_controller.dart new file mode 100644 index 0000000000000000000000000000000000000000..4cacf2b94518f28f4867ee33534c146cf948fb80 --- /dev/null +++ b/lib/src/runner/live_suite_controller.dart @@ -0,0 +1,155 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:async/async.dart' hide Result; +import 'package:collection/collection.dart'; + +import '../backend/state.dart'; +import '../backend/live_test.dart'; +import 'live_suite.dart'; +import 'runner_suite.dart'; + +/// An implementation of [LiveSuite] that's controlled by a +/// [LiveSuiteController]. +class _LiveSuite extends LiveSuite { + final LiveSuiteController _controller; + + RunnerSuite get suite => _controller._suite; + + bool get isComplete => _controller._isComplete; + + Future get onComplete => _controller._onCompleteGroup.future; + + bool get isClosed => _controller._onCloseCompleter.isCompleted; + + Future get onClose => _controller._onCloseCompleter.future; + + Stream<LiveTest> get onTestStarted => + _controller._onTestStartedController.stream; + + Set<LiveTest> get passed => new UnmodifiableSetView(_controller._passed); + + Set<LiveTest> get skipped => new UnmodifiableSetView(_controller._skipped); + + Set<LiveTest> get failed => new UnmodifiableSetView(_controller._failed); + + LiveTest get active => _controller._active; + + _LiveSuite(this._controller); +} + +/// A controller that drives a [LiveSuite]. +/// +/// This is a utility class to make it easier for [Engine] to create the +/// [LiveSuite]s exposed by various APIs. The [LiveSuite] is accessible through +/// [LiveSuiteController.liveSuite]. When a live test is run, it should be +/// passed to [reportLiveTest], and once tests are finished being run for this +/// suite, [noMoreLiveTests] should be called. Once the suite should be torn +/// down, [close] should be called. +class LiveSuiteController { + /// The [LiveSuite] controlled by [this]. + LiveSuite get liveSuite => _liveSuite; + LiveSuite _liveSuite; + + /// The suite that's being run. + final RunnerSuite _suite; + + /// The future group that backs [LiveSuite.onComplete]. + /// + /// This contains all the futures from tests that are run in this suite. + final _onCompleteGroup = new FutureGroup(); + + /// Whether [_onCompleteGroup]'s future has fired. + var _isComplete = false; + + /// The completer that backs [LiveSuite.onClose]. + /// + /// This is completed when the live suite is closed. + final _onCloseCompleter = new Completer(); + + /// The controller for [LiveSuite.onTestStarted]. + final _onTestStartedController = + new StreamController<LiveTest>.broadcast(sync: true); + + /// The set that backs [LiveTest.passed]. + final _passed = new Set<LiveTest>(); + + /// The set that backs [LiveTest.skipped]. + final _skipped = new Set<LiveTest>(); + + /// The set that backs [LiveTest.failed]. + final _failed = new Set<LiveTest>(); + + /// The test exposed through [LiveTest.active]. + LiveTest _active; + + /// Creates a controller for a live suite representing running the tests in + /// [suite]. + /// + /// Once this is called, the controller assumes responsibility for closing the + /// suite. The caller should call [LiveSuiteController.close] rather than + /// calling [RunnerSuite.close] directly. + LiveSuiteController(this._suite) { + _liveSuite = new _LiveSuite(this); + + _onCompleteGroup.future.then((_) { + _isComplete = true; + }, onError: (_) {}); + } + + /// Reports the status of [liveTest] through [liveSuite]. + /// + /// The live test is assumed to be a member of this suite. If [countSuccess] + /// is `true` (the default), the test is put into [passed] if it succeeds. + /// Otherwise, it's removed from [liveTests] entirely. + /// + /// Throws a [StateError] if called after [noMoreLiveTests]. + void reportLiveTest(LiveTest liveTest, {bool countSuccess: true}) { + if (_onTestStartedController.isClosed) { + throw new StateError("Can't call reportLiveTest() after noMoreTests()."); + } + + assert(liveTest.suite == _suite); + assert(_active == null); + + _active = liveTest; + + liveTest.onStateChange.listen((state) { + if (state.status != Status.complete) return; + _active = null; + + if (state.result != Result.success) { + _passed.remove(liveTest); + _failed.add(liveTest); + } else if (liveTest.test.metadata.skip) { + _skipped.add(liveTest); + } else if (countSuccess) { + _passed.add(liveTest); + } + }); + + _onTestStartedController.add(liveTest); + + _onCompleteGroup.add(liveTest.onComplete); + } + + /// Indicates that all the live tests that are going to be provided for this + /// suite have already been provided. + void noMoreLiveTests() { + _onTestStartedController.close(); + _onCompleteGroup.close(); + } + + /// Closes the underlying suite. + Future close() => _closeMemo.runOnce(() async { + try { + await _suite.close(); + } finally { + _onCloseCompleter.complete(); + } + }); + final _closeMemo = new AsyncMemoizer(); +} diff --git a/lib/src/util/iterable_set.dart b/lib/src/util/iterable_set.dart new file mode 100644 index 0000000000000000000000000000000000000000..5166a725a0a8253db399ca6afb1f6375f093b688 --- /dev/null +++ b/lib/src/util/iterable_set.dart @@ -0,0 +1,35 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:collection'; + +import 'package:collection/collection.dart'; + +/// An unmodifiable [Set] view backed by an arbitrary [Iterable]. +/// +/// Note that contrary to most APIs that take iterables, this does not convert +/// its argument to another collection before use. This means that if it's +/// lazily-generated, that generation will happen for every operation. +/// +/// Note also that set operations that are usually expected to be `O(1)` or +/// `O(log(n))`, such as [contains], may be `O(n)` for many underlying iterable +/// types. As such, this should only be used for small iterables. +class IterableSet<E> extends SetMixin<E> with UnmodifiableSetMixin<E> { + /// The base iterable that set operations forward to. + final Iterable<E> _base; + + int get length => _base.length; + + Iterator<E> get iterator => _base.iterator; + + /// Creates a [Set] view of [base]. + IterableSet(this._base); + + bool contains(Object element) => _base.contains(element); + + E lookup(Object needle) => + _base.firstWhere((element) => element == needle, orElse: () => null); + + Set<E> toSet() => _base.toSet(); +} diff --git a/pubspec.yaml b/pubspec.yaml index abe3c5080b63803beed2b00a48d8634d04321c56..bbe7800ca5cdb8219b84e48a4be9bf3d98039cd9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: async: '^1.8.0' barback: '>=0.14.0 <0.16.0' boolean_selector: '^1.0.0' - collection: '^1.1.0' + collection: '^1.6.0' glob: '^1.0.0' http_multi_server: '>=1.0.0 <3.0.0' path: '^1.2.0' diff --git a/test/io.dart b/test/io.dart index f7ead650692c26604599182f08e457ca574c7e79..00bb3e0c12bf801fd3ab5a5e59ad6babd7bbf560 100644 --- a/test/io.dart +++ b/test/io.dart @@ -7,6 +7,7 @@ library test.test.io; import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:path/path.dart' as p; @@ -15,6 +16,7 @@ import 'package:scheduled_test/scheduled_process.dart'; import 'package:scheduled_test/scheduled_stream.dart'; import 'package:scheduled_test/scheduled_test.dart'; import 'package:test/src/util/io.dart'; +import 'package:yaml/yaml.dart'; /// The path to the root directory of the `test` package. final String packageDir = p.dirname(p.dirname(libraryPath(#test.test.io))); diff --git a/test/runner/configuration/top_level_test.dart b/test/runner/configuration/top_level_test.dart index 8a219513aaa70b681ec26ed37b0f3fce49f4ec18..3f1f571762dca7b4d9df3192e0fe3f953ae2a157 100644 --- a/test/runner/configuration/top_level_test.dart +++ b/test/runner/configuration/top_level_test.dart @@ -334,7 +334,7 @@ transformers: test.stdout.expect(consumeThrough(contains('+1: All tests passed!'))); test.shouldExit(0); pub.kill(); - }); + }, tags: 'pub'); test("uses the specified concurrency", () { d.file("dart_test.yaml", JSON.encode({ diff --git a/test/runner/json_reporter_test.dart b/test/runner/json_reporter_test.dart index 31168fc485c0d7bf3a92f5a495bec793fbc1a660..401014d12b40c285bfca3545462e26a3474a810a 100644 --- a/test/runner/json_reporter_test.dart +++ b/test/runner/json_reporter_test.dart @@ -413,7 +413,7 @@ void _expectReport(String tests, List<Map> expected) { var stdoutLines = await test.stdoutStream().toList(); expect(stdoutLines.length, equals(expected.length), - reason: "Expected $stdoutLines to match $expected."); + reason: "Expected $stdoutLines to match ${JSON.encode(expected)}."); // TODO(nweiz): validate each event against the JSON schema when // patefacio/json_schema#4 is merged. diff --git a/test/runner/pub_serve_test.dart b/test/runner/pub_serve_test.dart index f6df2536edaebe63a39027193f699ae2aa3aa621..34d277205f8131f4a670ec6c1cd4cf70abc1f9be 100644 --- a/test/runner/pub_serve_test.dart +++ b/test/runner/pub_serve_test.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. @TestOn("vm") +@Tags(const ["pub"]) import 'dart:async'; import 'dart:io';