Line data Source code
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 : import 'package:stream_channel/stream_channel.dart'; 9 : import 'package:test_api/src/backend/group.dart'; // ignore: implementation_imports 10 : import 'package:test_api/src/backend/suite.dart'; // ignore: implementation_imports 11 : import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports 12 : import 'package:test_api/src/backend/test.dart'; // ignore: implementation_imports 13 : 14 : import 'environment.dart'; 15 : import 'suite.dart'; 16 : 17 : /// A suite produced and consumed by the test runner that has runner-specific 18 : /// logic and lifecycle management. 19 : /// 20 : /// This is separated from [Suite] because the backend library (which will 21 : /// eventually become its own package) is primarily for test code itself to use, 22 : /// for which the [RunnerSuite] APIs don't make sense. 23 : /// 24 : /// A [RunnerSuite] can be produced and controlled using a 25 : /// [RunnerSuiteController]. 26 : class RunnerSuite extends Suite { 27 : final RunnerSuiteController _controller; 28 : 29 : /// The environment in which this suite runs. 30 0 : Environment get environment => _controller._environment; 31 : 32 : /// The configuration for this suite. 33 0 : SuiteConfiguration get config => _controller._config; 34 : 35 : /// Whether the suite is paused for debugging. 36 : /// 37 : /// When using a dev inspector, this may also mean that the entire browser is 38 : /// paused. 39 0 : bool get isDebugging => _controller._isDebugging; 40 : 41 : /// A broadcast stream that emits an event whenever the suite is paused for 42 : /// debugging or resumed afterwards. 43 : /// 44 : /// The event is `true` when debugging starts and `false` when it ends. 45 0 : Stream<bool> get onDebugging => _controller._onDebuggingController.stream; 46 : 47 : /// A shortcut constructor for creating a [RunnerSuite] that never goes into 48 : /// debugging mode and doesn't support suite channels. 49 0 : factory RunnerSuite(Environment environment, SuiteConfiguration config, 50 : Group group, SuitePlatform platform, 51 : {String? path, Function()? onClose}) { 52 : var controller = 53 0 : RunnerSuiteController._local(environment, config, onClose: onClose); 54 0 : var suite = RunnerSuite._(controller, group, platform, path: path); 55 0 : controller._suite = Future.value(suite); 56 : return suite; 57 : } 58 : 59 0 : RunnerSuite._(this._controller, Group group, SuitePlatform platform, 60 : {String? path}) 61 0 : : super(group, platform, path: path); 62 : 63 0 : @override 64 : RunnerSuite filter(bool Function(Test) callback) { 65 0 : var filtered = group.filter(callback); 66 0 : filtered ??= Group.root([], metadata: metadata); 67 0 : return RunnerSuite._(_controller, filtered, platform, path: path); 68 : } 69 : 70 : /// Closes the suite and releases any resources associated with it. 71 0 : Future close() => _controller._close(); 72 : 73 : /// Collects a hit-map containing merged coverage. 74 : /// 75 : /// Result is suitable for input to the coverage formatters provided by 76 : /// `package:coverage`. 77 0 : Future<Map<String, dynamic>> gatherCoverage() async => 78 0 : (await _controller._gatherCoverage?.call()) ?? {}; 79 : } 80 : 81 : /// A class that exposes and controls a [RunnerSuite]. 82 : class RunnerSuiteController { 83 : /// The suite controlled by this controller. 84 0 : Future<RunnerSuite> get suite => _suite; 85 : late final Future<RunnerSuite> _suite; 86 : 87 : /// The backing value for [suite.environment]. 88 : final Environment _environment; 89 : 90 : /// The configuration for this suite. 91 : final SuiteConfiguration _config; 92 : 93 : /// A channel that communicates with the remote suite. 94 : final MultiChannel? _suiteChannel; 95 : 96 : /// The function to call when the suite is closed. 97 : final Function()? _onClose; 98 : 99 : /// The backing value for [suite.isDebugging]. 100 : bool _isDebugging = false; 101 : 102 : /// The controller for [suite.onDebugging]. 103 : final _onDebuggingController = StreamController<bool>.broadcast(); 104 : 105 : /// The channel names that have already been used. 106 : final _channelNames = <String>{}; 107 : 108 : /// Collects a hit-map containing merged coverage. 109 : final Future<Map<String, dynamic>> Function()? _gatherCoverage; 110 : 111 0 : RunnerSuiteController(this._environment, this._config, this._suiteChannel, 112 : Future<Group> groupFuture, SuitePlatform platform, 113 : {String? path, 114 : Function()? onClose, 115 : Future<Map<String, dynamic>> Function()? gatherCoverage}) 116 : : _onClose = onClose, 117 : _gatherCoverage = gatherCoverage { 118 0 : _suite = groupFuture 119 0 : .then((group) => RunnerSuite._(this, group, platform, path: path)); 120 : } 121 : 122 : /// Used by [new RunnerSuite] to create a runner suite that's not loaded from 123 : /// an external source. 124 0 : RunnerSuiteController._local(this._environment, this._config, 125 : {Function()? onClose, 126 : Future<Map<String, dynamic>> Function()? gatherCoverage}) 127 : : _suiteChannel = null, 128 : _onClose = onClose, 129 : _gatherCoverage = gatherCoverage; 130 : 131 : /// Sets whether the suite is paused for debugging. 132 : /// 133 : /// If this is different than [suite.isDebugging], this will automatically 134 : /// send out an event along [suite.onDebugging]. 135 0 : void setDebugging(bool debugging) { 136 0 : if (debugging == _isDebugging) return; 137 0 : _isDebugging = debugging; 138 0 : _onDebuggingController.add(debugging); 139 : } 140 : 141 : /// Returns a channel that communicates with the remote suite. 142 : /// 143 : /// This connects to a channel created by code in the test worker calling the 144 : /// `suiteChannel` argument from a `beforeLoad` callback to `serializeSuite` 145 : /// with the same name. 146 : /// It can be used used to send and receive any JSON-serializable object. 147 : /// 148 : /// This is exposed on the [RunnerSuiteController] so that runner plugins can 149 : /// communicate with the workers they spawn before the associated [suite] is 150 : /// fully loaded. 151 0 : StreamChannel channel(String name) { 152 0 : if (!_channelNames.add(name)) { 153 0 : throw StateError('Duplicate RunnerSuite.channel() connection "$name".'); 154 : } 155 : 156 0 : var suiteChannel = _suiteChannel; 157 : if (suiteChannel == null) { 158 0 : throw StateError('No suite channel set up but one was requested.'); 159 : } 160 : 161 0 : var channel = suiteChannel.virtualChannel(); 162 0 : suiteChannel.sink 163 0 : .add({'type': 'suiteChannel', 'name': name, 'id': channel.id}); 164 : return channel; 165 : } 166 : 167 : /// The backing function for [suite.close]. 168 0 : Future _close() => _closeMemo.runOnce(() async { 169 0 : await _onDebuggingController.close(); 170 0 : var onClose = _onClose; 171 0 : if (onClose != null) await onClose(); 172 : }); 173 : final _closeMemo = AsyncMemoizer(); 174 : }