Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(754)

Side by Side Diff: lib/src/runner.dart

Issue 1248073003: Add a --pause-after-load flag for debugging. (Closed) Base URL: git@github.com:dart-lang/test@master
Patch Set: Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library test.runner; 5 library test.runner;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 import 'dart:io'; 8 import 'dart:io';
9 9
10 import 'package:async/async.dart';
11
12 import 'backend/metadata.dart'; 10 import 'backend/metadata.dart';
11 import 'backend/suite.dart';
12 import 'backend/test_platform.dart';
13 import 'runner/application_exception.dart'; 13 import 'runner/application_exception.dart';
14 import 'runner/configuration.dart'; 14 import 'runner/configuration.dart';
15 import 'runner/engine.dart'; 15 import 'runner/engine.dart';
16 import 'runner/load_exception.dart'; 16 import 'runner/load_exception.dart';
17 import 'runner/load_suite.dart'; 17 import 'runner/load_suite.dart';
18 import 'runner/loader.dart'; 18 import 'runner/loader.dart';
19 import 'runner/reporter.dart';
19 import 'runner/reporter/compact.dart'; 20 import 'runner/reporter/compact.dart';
20 import 'runner/reporter/expanded.dart'; 21 import 'runner/reporter/expanded.dart';
21 import 'util/async_thunk.dart'; 22 import 'util/async_thunk.dart';
23 import 'util/io.dart';
22 import 'utils.dart'; 24 import 'utils.dart';
23 25
26 /// The set of platforms for which debug flags are (currently) not supported.
27 final _debugUnsupportedPlatforms = new Set.from(
28 [TestPlatform.vm, TestPlatform.phantomJS, TestPlatform.contentShell]);
29
24 /// A class that loads and runs tests based on a [Configuration]. 30 /// A class that loads and runs tests based on a [Configuration].
25 /// 31 ///
26 /// This maintains a [Loader] and an [Engine] and passes test suites from one to 32 /// This maintains a [Loader] and an [Engine] and passes test suites from one to
27 /// the other, as well as printing out tests with a [CompactReporter] or an 33 /// the other, as well as printing out tests with a [CompactReporter] or an
28 /// [ExpandedReporter]. 34 /// [ExpandedReporter].
29 class Runner { 35 class Runner {
30 /// The configuration for the runner. 36 /// The configuration for the runner.
31 final Configuration _configuration; 37 final Configuration _configuration;
32 38
33 /// The loader that loads the test suites from the filesystem. 39 /// The loader that loads the test suites from the filesystem.
34 final Loader _loader; 40 final Loader _loader;
35 41
36 /// The engine that runs the test suites. 42 /// The engine that runs the test suites.
37 final Engine _engine; 43 final Engine _engine;
38 44
45 /// The reporter that's emitting the test runner's results.
46 final Reporter _reporter;
47
48 /// The subscription to the stream returned by [_loadSuites].
49 StreamSubscription _suiteSubscription;
50
39 /// The thunk for ensuring [close] only runs once. 51 /// The thunk for ensuring [close] only runs once.
40 final _closeThunk = new AsyncThunk(); 52 final _closeThunk = new AsyncThunk();
41 bool get _closed => _closeThunk.hasRun; 53 bool get _closed => _closeThunk.hasRun;
42 54
43 /// Whether [run] has been called.
44 bool _hasRun = false;
45
46 /// Creates a new runner based on [configuration]. 55 /// Creates a new runner based on [configuration].
47 factory Runner(Configuration configuration) { 56 factory Runner(Configuration configuration) {
48 var metadata = new Metadata( 57 var metadata = new Metadata(
49 verboseTrace: configuration.verboseTrace); 58 verboseTrace: configuration.verboseTrace);
50 var loader = new Loader(configuration.platforms, 59 var loader = new Loader(configuration.platforms,
51 pubServeUrl: configuration.pubServeUrl, 60 pubServeUrl: configuration.pubServeUrl,
52 packageRoot: configuration.packageRoot, 61 packageRoot: configuration.packageRoot,
53 color: configuration.color, 62 color: configuration.color,
54 metadata: metadata, 63 metadata: metadata,
55 jsTrace: configuration.jsTrace); 64 jsTrace: configuration.jsTrace);
56 65
57 var engine = new Engine(concurrency: configuration.concurrency); 66 var engine = new Engine(concurrency: configuration.concurrency);
58 67
59 var watch = configuration.reporter == "compact" 68 var watch = configuration.reporter == "compact"
60 ? CompactReporter.watch 69 ? CompactReporter.watch
61 : ExpandedReporter.watch; 70 : ExpandedReporter.watch;
62 71
63 watch( 72 var reporter = watch(
64 engine, 73 engine,
65 color: configuration.color, 74 color: configuration.color,
66 verboseTrace: configuration.verboseTrace, 75 verboseTrace: configuration.verboseTrace,
67 printPath: configuration.paths.length > 1 || 76 printPath: configuration.paths.length > 1 ||
68 new Directory(configuration.paths.single).existsSync(), 77 new Directory(configuration.paths.single).existsSync(),
69 printPlatform: configuration.platforms.length > 1); 78 printPlatform: configuration.platforms.length > 1);
70 79
71 return new Runner._(configuration, loader, engine); 80 return new Runner._(configuration, loader, engine, reporter);
72 } 81 }
73 82
74 Runner._(this._configuration, this._loader, this._engine); 83 Runner._(this._configuration, this._loader, this._engine, this._reporter);
75 84
76 /// Starts the runner. 85 /// Starts the runner.
77 /// 86 ///
78 /// This starts running tests and printing their progress. It returns whether 87 /// This starts running tests and printing their progress. It returns whether
79 /// or not they ran successfully. 88 /// or not they ran successfully.
80 Future<bool> run() async { 89 Future<bool> run() async {
81 _hasRun = true;
82
83 if (_closed) { 90 if (_closed) {
84 throw new StateError("run() may not be called on a closed Runner."); 91 throw new StateError("run() may not be called on a closed Runner.");
85 } 92 }
86 93
94 var suites = _loadSuites();
95
87 var success; 96 var success;
88 var results = await Future.wait([ 97 if (_configuration.pauseAfterLoad) {
89 _loadSuites(), 98 // TODO(nweiz): disable timeouts when debugging.
90 _engine.run() 99 success = await _loadThenPause(suites);
91 ], eagerError: true); 100 } else {
92 success = results.last; 101 _suiteSubscription = suites.listen(_engine.suiteSink.add);
102 var results = await Future.wait([
103 _suiteSubscription.asFuture().then((_) => _engine.suiteSink.close()),
104 _engine.run()
105 ], eagerError: true);
106 success = results.last;
107 }
93 108
94 if (_closed) return false; 109 if (_closed) return false;
95 110
96 if (_engine.passed.length == 0 && _engine.failed.length == 0 && 111 if (_engine.passed.length == 0 && _engine.failed.length == 0 &&
97 _engine.skipped.length == 0 && _configuration.pattern != null) { 112 _engine.skipped.length == 0 && _configuration.pattern != null) {
98 var message = 'No tests match '; 113 var message = 'No tests match ';
99 114
100 if (_configuration.pattern is RegExp) { 115 if (_configuration.pattern is RegExp) {
101 var pattern = (_configuration.pattern as RegExp).pattern; 116 var pattern = (_configuration.pattern as RegExp).pattern;
102 message += 'regular expression "$pattern".'; 117 message += 'regular expression "$pattern".';
103 } else { 118 } else {
104 message += '"${_configuration.pattern}".'; 119 message += '"${_configuration.pattern}".';
105 } 120 }
106 throw new ApplicationException(message); 121 throw new ApplicationException(message);
107 } 122 }
108 123
109 // Explicitly check "== true" here because [Engine.run] can return `null` 124 // Explicitly check "== true" here because [Engine.run] can return `null`
110 // if the engine was closed prematurely. 125 // if the engine was closed prematurely.
111 return success == true; 126 return success == true;
112 } 127 }
113 128
114 /// Closes the runner. 129 /// Closes the runner.
115 /// 130 ///
116 /// This stops any future test suites from running. It will wait for any 131 /// This stops any future test suites from running. It will wait for any
117 /// currently-running VM tests, in case they have stuff to clean up on the 132 /// currently-running VM tests, in case they have stuff to clean up on the
118 /// filesystem. 133 /// filesystem.
119 Future close() => _closeThunk.run(() async { 134 Future close() => _closeThunk.run(() async {
120 var timer; 135 var timer;
121 if (_hasRun) { 136 if (!_engine.isIdle) {
122 // Wait a bit to print this message, since printing it eagerly looks weird 137 // Wait a bit to print this message, since printing it eagerly looks weird
123 // if the tests then finish immediately. 138 // if the tests then finish immediately.
124 timer = new Timer(new Duration(seconds: 1), () { 139 timer = new Timer(new Duration(seconds: 1), () {
125 // Print a blank line first to ensure that this doesn't interfere with 140 // Pause the reporter while we print to ensure that we don't interfere
126 // the compact reporter's unfinished line. 141 // with its output.
127 print(''); 142 _reporter.pause();
128 print("Waiting for current test(s) to finish."); 143 print("Waiting for current test(s) to finish.");
129 print("Press Control-C again to terminate immediately."); 144 print("Press Control-C again to terminate immediately.");
145 _reporter.resume();
130 }); 146 });
131 } 147 }
132 148
149 if (_suiteSubscription != null) _suiteSubscription.cancel();
150 _suiteSubscription = null;
151
133 // Make sure we close the engine *before* the loader. Otherwise, 152 // Make sure we close the engine *before* the loader. Otherwise,
134 // LoadSuites provided by the loader may get into bad states. 153 // LoadSuites provided by the loader may get into bad states.
135 await _engine.close(); 154 await _engine.close();
136 if (timer != null) timer.cancel(); 155 if (timer != null) timer.cancel();
137 await _loader.close(); 156 await _loader.close();
138 }); 157 });
139 158
140 /// Load the test suites in [_configuration.paths] that match 159 /// Return a stream of [LoadSuite]s in [_configuration.paths].
141 /// [_configuration.pattern]. 160 ///
142 Future _loadSuites() async { 161 /// Only tests that match [_configuration.pattern] will be included in the
143 var group = new FutureGroup(); 162 /// suites once they're loaded.
144 163 Stream<LoadSuite> _loadSuites() {
145 mergeStreams(_configuration.paths.map((path) { 164 return mergeStreams(_configuration.paths.map((path) {
146 if (new Directory(path).existsSync()) return _loader.loadDir(path); 165 if (new Directory(path).existsSync()) return _loader.loadDir(path);
147 if (new File(path).existsSync()) return _loader.loadFile(path); 166 if (new File(path).existsSync()) return _loader.loadFile(path);
148 167
149 return new Stream.fromIterable([ 168 return new Stream.fromIterable([
150 new LoadSuite("loading $path", () => 169 new LoadSuite("loading $path", () =>
151 throw new LoadException(path, 'Does not exist.')) 170 throw new LoadException(path, 'Does not exist.'))
152 ]); 171 ]);
153 })).listen((loadSuite) { 172 })).map((loadSuite) {
154 group.add(new Future.sync(() { 173 return loadSuite.changeSuite((suite) {
155 _engine.suiteSink.add(loadSuite.changeSuite((suite) { 174 if (_configuration.pattern == null) return suite;
156 if (_configuration.pattern == null) return suite; 175 return suite.change(tests: suite.tests.where((test) =>
157 return suite.change(tests: suite.tests.where( 176 test.name.contains(_configuration.pattern)));
158 (test) => test.name.contains(_configuration.pattern))); 177 });
159 })); 178 });
160 })); 179 }
161 }, onError: (error, stackTrace) {
162 group.add(new Future.error(error, stackTrace));
163 }, onDone: group.close);
164 180
165 await group.future; 181 /// Loads each suite in [suites] in order, pausing after load for platforms
182 /// that support debugging.
183 Future<bool> _loadThenPause(Stream<LoadSuite> suites) async {
184 var unsupportedPlatforms = _configuration.platforms
185 .where(_debugUnsupportedPlatforms.contains)
186 .map((platform) =>
187 platform == TestPlatform.vm ? "the Dart VM" : platform.name)
188 .toList();
166 189
167 // Once we've loaded all the suites, notify the engine that no more will be 190 if (unsupportedPlatforms.isNotEmpty) {
168 // coming. 191 warn(
169 _engine.suiteSink.close(); 192 wordWrap("Debugging is currently unsupported on "
193 "${toSentence(unsupportedPlatforms)}."),
194 color: _configuration.color);
195 }
196
197 _suiteSubscription = suites.asyncMap((loadSuite) async {
198 // Make the underlying suite null so that the engine doesn't start running
199 // it immediately.
200 _engine.suiteSink.add(loadSuite.changeSuite((_) => null));
201
202 var suite = await loadSuite.suite;
203 if (suite == null) return;
204
205 await _pause(suite);
206 if (_closed) return;
207
208 _engine.suiteSink.add(suite);
209 await _engine.onIdle.first;
210 }).listen(null);
211
212 var results = await Future.wait([
213 _suiteSubscription.asFuture().then((_) => _engine.suiteSink.close()),
214 _engine.run()
215 ]);
216 return results.last;
217 }
218
219 /// Pauses the engine and the reporter so that the user can set breakpoints as
220 /// necessary.
221 ///
222 /// This is a no-op for test suites that aren't on platforms where debugging
223 /// is supported.
224 Future _pause(Suite suite) async {
225 if (suite.platform == null) return;
226 if (_debugUnsupportedPlatforms.contains(suite.platform)) return;
227
228 try {
229 _reporter.pause();
230
231 var bold = _configuration.color ? '\u001b[1m' : '';
232 var noColor = _configuration.color ? '\u001b[0m' : '';
233 print('');
234 print(wordWrap(
235 "${bold}The test runner is paused.${noColor} Open the dev console in "
236 "${suite.platform} and set breakpoints. Once you're finished, "
237 "return to this terminal and press Enter."));
238
239 // TODO(nweiz): Display something in the paused browsers indicating that
240 // they're paused.
241
242 await stdinLines.next;
243 } finally {
244 _reporter.resume();
245 }
170 } 246 }
171 } 247 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698