Shared loading and running pool (#1161)
Change the behavior of the concurrency setting so that loading and running
don't have separate pools.
* The loading and running of a test are now done with the same resource, and
the concurrency setting uniformly affects each. With `-j1` only a single
test will ever be loaded at a time.
* Previously the loading pool was 2x larger than the actual concurrency
setting which could cause flaky tests due to tests being loaded while
other tests were running, even with `-j1`.
diff --git a/pkgs/test/test/runner/browser/compact_reporter_test.dart b/pkgs/test/test/runner/browser/compact_reporter_test.dart
index 513229d..4a65f92 100644
--- a/pkgs/test/test/runner/browser/compact_reporter_test.dart
+++ b/pkgs/test/test/runner/browser/compact_reporter_test.dart
@@ -26,7 +26,7 @@
['-p', 'chrome', '-p', 'vm', '-j', '1', 'test.dart'],
reporter: 'compact');
- expect(test.stdout, containsInOrder(['[VM]', '[Chrome]']));
+ expect(test.stdout, containsInOrder(['[Chrome]', '[VM]']));
await test.shouldExit(0);
}, tags: 'chrome');
}
diff --git a/pkgs/test/test/runner/configuration/platform_test.dart b/pkgs/test/test/runner/configuration/platform_test.dart
index 79a9fe0..d9bcede 100644
--- a/pkgs/test/test/runner/configuration/platform_test.dart
+++ b/pkgs/test/test/runner/configuration/platform_test.dart
@@ -69,8 +69,8 @@
expect(
test.stdout,
containsInOrder([
- '-1: [VM] test [E]',
- '-2: [Chrome] test [E]',
+ '-1: [Chrome] test [E]',
+ '-2: [VM] test [E]',
'-2: Some tests failed.'
]));
await test.shouldExit(1);
diff --git a/pkgs/test/test/runner/engine_test.dart b/pkgs/test/test/runner/engine_test.dart
index 08c3b00..0546228 100644
--- a/pkgs/test/test/runner/engine_test.dart
+++ b/pkgs/test/test/runner/engine_test.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
+import 'dart:math';
import 'package:pedantic/pedantic.dart';
import 'package:test/test.dart';
@@ -275,4 +276,58 @@
return engine.run();
});
});
+
+ group('concurrency', () {
+ test('is shared between runner and load suites', () async {
+ for (var concurrency = 1; concurrency < 5; concurrency++) {
+ var testsLoaded = 0;
+ var maxLoadConcurrency = 0;
+ var testsRunning = 0;
+ var maxTestConcurrency = 0;
+ var testCount = concurrency * 2;
+
+ Future<void> updateAndCheckConcurrency(
+ {bool isLoadSuite = false}) async {
+ if (isLoadSuite) {
+ testsLoaded++;
+ maxLoadConcurrency = max(maxLoadConcurrency, testsLoaded);
+ expect(testsLoaded, lessThanOrEqualTo(concurrency));
+ } else {
+ testsRunning++;
+ maxTestConcurrency = max(maxTestConcurrency, testsRunning);
+ expect(testsRunning, lessThanOrEqualTo(concurrency));
+ }
+ // Simulate the test/loading taking some amount of time so that
+ // we actually reach max concurrency.
+ await Future.delayed(Duration(milliseconds: 100));
+ if (!isLoadSuite) {
+ testsRunning--;
+ testsLoaded--;
+ }
+ }
+
+ var tests = declare(() {
+ for (var i = 0; i < testCount; i++) {
+ test('test ${i + 1}', () async {
+ await updateAndCheckConcurrency();
+ });
+ }
+ });
+ var engine = Engine.withSuites([
+ for (var i = 0; i < testCount; i++)
+ loadSuite('group $i', () async {
+ await updateAndCheckConcurrency(isLoadSuite: true);
+ return runnerSuite(Group.root([tests[i]]));
+ }),
+ ], concurrency: concurrency);
+
+ await engine.run();
+ expect(engine.liveTests.length, testCount);
+
+ // We should reach but not exceed max concurrency
+ expect(maxTestConcurrency, concurrency);
+ expect(maxLoadConcurrency, concurrency);
+ }
+ });
+ });
}
diff --git a/pkgs/test/test/utils.dart b/pkgs/test/test/utils.dart
index b2a288e..09b608d 100644
--- a/pkgs/test/test/utils.dart
+++ b/pkgs/test/test/utils.dart
@@ -19,6 +19,7 @@
import 'package:test_core/src/runner/suite.dart';
import 'package:test_core/src/runner/engine.dart';
import 'package:test_core/src/runner/plugin/environment.dart';
+import 'package:test_core/src/runner/load_suite.dart';
import 'package:test_core/src/runner/runner_suite.dart';
import 'package:test/test.dart';
@@ -201,3 +202,7 @@
/// Returns a [RunnerSuite] with a default environment and configuration.
RunnerSuite runnerSuite(Group root) => RunnerSuite(
const PluginEnvironment(), SuiteConfiguration.empty, root, suitePlatform);
+
+/// Returns a [LoadSuite] with a default configuration.
+LoadSuite loadSuite(String name, FutureOr<RunnerSuite> Function() body) =>
+ LoadSuite(name, SuiteConfiguration.empty, suitePlatform, body);
diff --git a/pkgs/test_core/CHANGELOG.md b/pkgs/test_core/CHANGELOG.md
index b36455a..bfc510c 100644
--- a/pkgs/test_core/CHANGELOG.md
+++ b/pkgs/test_core/CHANGELOG.md
@@ -9,6 +9,14 @@
* Support retrying of entire test suites when they fail to load.
* Fix the `compiling` message in precompiled mode so it says `loading` instead,
which is more accurate.
+* Change the behavior of the concurrency setting so that loading and running
+ don't have separate pools.
+ * The loading and running of a test are now done with the same resource, and
+ the concurrency setting uniformly affects each. With `-j1` only a single
+ test will ever be loaded at a time.
+ * Previously the loading pool was 2x larger than the actual concurrency
+ setting which could cause flaky tests due to tests being loaded while
+ other tests were running, even with `-j1`.
## 0.2.18
diff --git a/pkgs/test_core/lib/src/runner/engine.dart b/pkgs/test_core/lib/src/runner/engine.dart
index d944e50..c532c11 100644
--- a/pkgs/test_core/lib/src/runner/engine.dart
+++ b/pkgs/test_core/lib/src/runner/engine.dart
@@ -73,12 +73,6 @@
/// A pool that limits the number of test suites running concurrently.
final Pool _runPool;
- /// A pool that limits the number of test suites loaded concurrently.
- ///
- /// Once this reaches its limit, loading any additional test suites will cause
- /// previous suites to be unloaded in the order they completed.
- final Pool _loadPool;
-
/// A completer that will complete when [this] is unpaused.
///
/// If [this] isn't paused, [_pauseCompleter] is `null`.
@@ -96,8 +90,7 @@
/// This will be `null` if [close] was called before all the tests finished
/// running.
Future<bool> get success async {
- await Future.wait(<Future>[_group.future, _loadPool.done],
- eagerError: true);
+ await Future.wait(<Future>[_group.future, _runPool.done], eagerError: true);
if (_closedBeforeDone) return null;
return liveTests.every((liveTest) =>
liveTest.state.result.isPassing &&
@@ -215,12 +208,10 @@
// dart:io is unavailable.
/// Creates an [Engine] that will run all tests provided via [suiteSink].
///
- /// [concurrency] controls how many suites are run at once, and defaults to 1.
- /// [maxSuites] controls how many suites are *loaded* at once, and defaults to
- /// four times [concurrency].
- Engine({int concurrency, int maxSuites, String coverage})
+ /// [concurrency] controls how many suites are loaded and ran at once, and
+ /// defaults to 1.
+ Engine({int concurrency, String coverage})
: _runPool = Pool(concurrency ?? 1),
- _loadPool = Pool(maxSuites ?? (concurrency ?? 1) * 2),
_coverage = coverage {
_group.future.then((_) {
_onTestStartedGroup.close();
@@ -265,35 +256,32 @@
_onSuiteAddedController.add(suite);
_group.add(() async {
- var loadResource = await _loadPool.request();
-
+ var resource = await _runPool.request();
LiveSuiteController controller;
- if (suite is LoadSuite) {
- await _onUnpaused;
- controller = await _addLoadSuite(suite);
- if (controller == null) {
- loadResource.release();
- return;
+ try {
+ if (suite is LoadSuite) {
+ await _onUnpaused;
+ controller = await _addLoadSuite(suite);
+ if (controller == null) return;
+ } else {
+ controller = LiveSuiteController(suite);
}
- } else {
- controller = LiveSuiteController(suite);
- }
- _addLiveSuite(controller.liveSuite);
+ _addLiveSuite(controller.liveSuite);
- await _runPool.withResource(() async {
if (_closed) return;
await _runGroup(controller, controller.liveSuite.suite.group, []);
controller.noMoreLiveTests();
if (_coverage != null) await writeCoverage(_coverage, controller);
- loadResource.allowRelease(() => controller.close());
- });
+ } finally {
+ resource.allowRelease(() => controller?.close());
+ }
}());
}, onDone: () {
_subscriptions.remove(subscription);
_onSuiteAddedController.close();
_group.close();
- _loadPool.close();
+ _runPool.close();
});
_subscriptions.add(subscription);
@@ -558,11 +546,11 @@
var allLiveTests = liveTests.toSet()..addAll(_activeLoadTests);
var futures = allLiveTests.map((liveTest) => liveTest.close()).toList();
- // Closing the load pool will close the test suites as soon as their tests
+ // Closing the run pool will close the test suites as soon as their tests
// are done. For browser suites this is effectively immediate since their
// tests shut down as soon as they're closed, but for VM suites we may need
// to wait for tearDowns or tearDownAlls to run.
- futures.add(_loadPool.close());
+ futures.add(_runPool.close());
await Future.wait(futures, eagerError: true);
}
}