OLD | NEW |
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.loader; | 5 library test.runner.loader; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:io'; | 8 import 'dart:io'; |
9 import 'dart:isolate'; | 9 import 'dart:isolate'; |
10 | 10 |
11 import 'package:analyzer/analyzer.dart'; | 11 import 'package:analyzer/analyzer.dart'; |
12 import 'package:path/path.dart' as p; | 12 import 'package:path/path.dart' as p; |
13 | 13 |
14 import '../backend/metadata.dart'; | 14 import '../backend/metadata.dart'; |
15 import '../backend/suite.dart'; | 15 import '../backend/suite.dart'; |
16 import '../backend/test_platform.dart'; | 16 import '../backend/test_platform.dart'; |
17 import '../util/dart.dart'; | 17 import '../util/dart.dart'; |
18 import '../util/io.dart'; | 18 import '../util/io.dart'; |
| 19 import '../util/isolate_wrapper.dart'; |
19 import '../util/remote_exception.dart'; | 20 import '../util/remote_exception.dart'; |
20 import '../utils.dart'; | 21 import '../utils.dart'; |
21 import 'browser/server.dart'; | 22 import 'browser/server.dart'; |
22 import 'load_exception.dart'; | 23 import 'load_exception.dart'; |
23 import 'parse_metadata.dart'; | 24 import 'parse_metadata.dart'; |
24 import 'vm/isolate_test.dart'; | 25 import 'vm/isolate_test.dart'; |
25 | 26 |
26 /// A class for finding test files and loading them into a runnable form. | 27 /// A class for finding test files and loading them into a runnable form. |
27 class Loader { | 28 class Loader { |
28 /// All platforms for which tests should be loaded. | 29 /// All platforms for which tests should be loaded. |
29 final List<TestPlatform> _platforms; | 30 final List<TestPlatform> _platforms; |
30 | 31 |
31 /// Whether to enable colors for Dart compilation. | 32 /// Whether to enable colors for Dart compilation. |
32 final bool _color; | 33 final bool _color; |
33 | 34 |
34 /// The package root to use for loading tests, or `null` to use the automatic | 35 /// The package root to use for loading tests, or `null` to use the automatic |
35 /// root. | 36 /// root. |
36 final String _packageRoot; | 37 final String _packageRoot; |
37 | 38 |
| 39 /// The URL for the `pub serve` instance to use to load tests. |
| 40 /// |
| 41 /// This is `null` if tests should be loaded from the filesystem. |
| 42 final Uri _pubServeUrl; |
| 43 |
38 /// All isolates that have been spun up by the loader. | 44 /// All isolates that have been spun up by the loader. |
39 final _isolates = new Set<Isolate>(); | 45 final _isolates = new Set<Isolate>(); |
40 | 46 |
41 /// The server that serves browser test pages. | 47 /// The server that serves browser test pages. |
42 /// | 48 /// |
43 /// This is lazily initialized the first time it's accessed. | 49 /// This is lazily initialized the first time it's accessed. |
44 Future<BrowserServer> get _browserServer { | 50 Future<BrowserServer> get _browserServer { |
45 if (_browserServerCompleter == null) { | 51 if (_browserServerCompleter == null) { |
46 _browserServerCompleter = new Completer(); | 52 _browserServerCompleter = new Completer(); |
47 BrowserServer.start(packageRoot: _packageRoot, color: _color) | 53 BrowserServer.start( |
| 54 packageRoot: _packageRoot, |
| 55 pubServeUrl: _pubServeUrl, |
| 56 color: _color) |
48 .then(_browserServerCompleter.complete) | 57 .then(_browserServerCompleter.complete) |
49 .catchError(_browserServerCompleter.completeError); | 58 .catchError(_browserServerCompleter.completeError); |
50 } | 59 } |
51 return _browserServerCompleter.future; | 60 return _browserServerCompleter.future; |
52 } | 61 } |
53 Completer<BrowserServer> _browserServerCompleter; | 62 Completer<BrowserServer> _browserServerCompleter; |
54 | 63 |
55 /// Creates a new loader. | 64 /// Creates a new loader. |
56 /// | 65 /// |
57 /// If [packageRoot] is passed, it's used as the package root for all loaded | 66 /// If [packageRoot] is passed, it's used as the package root for all loaded |
58 /// tests. Otherwise, the `packages/` directories next to the test entrypoints | 67 /// tests. Otherwise, the `packages/` directories next to the test entrypoints |
59 /// will be used. | 68 /// will be used. |
60 /// | 69 /// |
| 70 /// If [pubServeUrl] is passed, tests will be loaded from the `pub serve` |
| 71 /// instance at that URL rather than from the filesystem. |
| 72 /// |
61 /// If [color] is true, console colors will be used when compiling Dart. | 73 /// If [color] is true, console colors will be used when compiling Dart. |
62 Loader(Iterable<TestPlatform> platforms, {String packageRoot, | 74 Loader(Iterable<TestPlatform> platforms, {String packageRoot, |
63 bool color: false}) | 75 Uri pubServeUrl, bool color: false}) |
64 : _platforms = platforms.toList(), | 76 : _platforms = platforms.toList(), |
| 77 _pubServeUrl = pubServeUrl, |
65 _packageRoot = packageRoot, | 78 _packageRoot = packageRoot, |
66 _color = color; | 79 _color = color; |
67 | 80 |
68 /// Loads all test suites in [dir]. | 81 /// Loads all test suites in [dir]. |
69 /// | 82 /// |
70 /// This will load tests from files that end in "_test.dart". | 83 /// This will load tests from files that end in "_test.dart". |
71 Future<List<Suite>> loadDir(String dir) { | 84 Future<List<Suite>> loadDir(String dir) { |
72 return Future.wait(new Directory(dir).listSync(recursive: true) | 85 return Future.wait(new Directory(dir).listSync(recursive: true) |
73 .map((entry) { | 86 .map((entry) { |
74 if (entry is! File) return new Future.value([]); | 87 if (entry is! File) return new Future.value([]); |
(...skipping 18 matching lines...) Expand all Loading... |
93 // the VM's or dart2js's. | 106 // the VM's or dart2js's. |
94 metadata = new Metadata(); | 107 metadata = new Metadata(); |
95 } on FormatException catch (error) { | 108 } on FormatException catch (error) { |
96 throw new LoadException(path, error); | 109 throw new LoadException(path, error); |
97 } | 110 } |
98 | 111 |
99 return Future.wait(_platforms.map((platform) { | 112 return Future.wait(_platforms.map((platform) { |
100 return new Future.sync(() { | 113 return new Future.sync(() { |
101 if (!metadata.testOn.evaluate(platform, os: currentOS)) return null; | 114 if (!metadata.testOn.evaluate(platform, os: currentOS)) return null; |
102 | 115 |
| 116 if (_pubServeUrl != null && !p.isWithin('test', path)) { |
| 117 throw new LoadException(path, |
| 118 'When using "pub serve", all test files must be in test/.'); |
| 119 } |
| 120 |
103 if (platform == TestPlatform.chrome) return _loadBrowserFile(path); | 121 if (platform == TestPlatform.chrome) return _loadBrowserFile(path); |
104 assert(platform == TestPlatform.vm); | 122 assert(platform == TestPlatform.vm); |
105 return _loadVmFile(path); | 123 return _loadVmFile(path); |
106 }).then((suite) { | 124 }).then((suite) { |
107 if (suite == null) return null; | 125 if (suite == null) return null; |
108 return suite.change(metadata: metadata).filter(platform, os: currentOS); | 126 return suite.change(metadata: metadata).filter(platform, os: currentOS); |
109 }); | 127 }); |
110 })).then((suites) => suites.where((suite) => suite != null).toList()); | 128 })).then((suites) => suites.where((suite) => suite != null).toList()); |
111 } | 129 } |
112 | 130 |
113 /// Load the test suite at [path] in a browser. | 131 /// Load the test suite at [path] in a browser. |
114 Future<Suite> _loadBrowserFile(String path) => | 132 Future<Suite> _loadBrowserFile(String path) => |
115 _browserServer.then((browserServer) => browserServer.loadSuite(path)); | 133 _browserServer.then((browserServer) => browserServer.loadSuite(path)); |
116 | 134 |
117 /// Load the test suite at [path] in VM isolate. | 135 /// Load the test suite at [path] in VM isolate. |
118 Future<Suite> _loadVmFile(String path) { | 136 Future<Suite> _loadVmFile(String path) { |
119 var packageRoot = packageRootFor(path, _packageRoot); | 137 var packageRoot = packageRootFor(path, _packageRoot); |
120 var receivePort = new ReceivePort(); | 138 var receivePort = new ReceivePort(); |
121 return runInIsolate(''' | 139 |
| 140 return new Future.sync(() { |
| 141 if (_pubServeUrl != null) { |
| 142 var url = _pubServeUrl.resolve( |
| 143 p.withoutExtension(p.relative(path, from: 'test')) + |
| 144 '.vm_test.dart'); |
| 145 return Isolate.spawnUri(url, [], {'reply': receivePort.sendPort}) |
| 146 .then((isolate) => new IsolateWrapper(isolate, () {})) |
| 147 .catchError((error, stackTrace) { |
| 148 if (error is! IsolateSpawnException) throw error; |
| 149 |
| 150 if (error.message.contains("OS Error: Connection refused")) { |
| 151 throw new LoadException(path, |
| 152 "Error getting $url: Connection refused\n" |
| 153 'Make sure "pub serve" is running.'); |
| 154 } else if (error.message.contains("404 Not Found")) { |
| 155 throw new LoadException(path, |
| 156 "Error getting $url: 404 Not Found\n" |
| 157 'Make sure "pub serve" is serving the test/ directory.'); |
| 158 } |
| 159 |
| 160 throw new LoadException(path, error); |
| 161 }); |
| 162 } else { |
| 163 return runInIsolate(''' |
122 import "package:test/src/runner/vm/isolate_listener.dart"; | 164 import "package:test/src/runner/vm/isolate_listener.dart"; |
123 | 165 |
124 import "${p.toUri(p.absolute(path))}" as test; | 166 import "${p.toUri(p.absolute(path))}" as test; |
125 | 167 |
126 void main(_, Map message) { | 168 void main(_, Map message) { |
127 var sendPort = message['reply']; | 169 var sendPort = message['reply']; |
128 IsolateListener.start(sendPort, () => test.main); | 170 IsolateListener.start(sendPort, () => test.main); |
129 } | 171 } |
130 ''', { | 172 ''', { |
131 'reply': receivePort.sendPort | 173 'reply': receivePort.sendPort |
132 }, packageRoot: packageRoot) | 174 }, packageRoot: packageRoot); |
133 .catchError((error, stackTrace) { | 175 } |
| 176 }).catchError((error, stackTrace) { |
134 receivePort.close(); | 177 receivePort.close(); |
| 178 if (error is LoadException) throw error; |
135 return new Future.error(new LoadException(path, error), stackTrace); | 179 return new Future.error(new LoadException(path, error), stackTrace); |
136 }).then((isolate) { | 180 }).then((isolate) { |
137 _isolates.add(isolate); | 181 _isolates.add(isolate); |
138 return receivePort.first; | 182 return receivePort.first; |
139 }).then((response) { | 183 }).then((response) { |
140 if (response["type"] == "loadException") { | 184 if (response["type"] == "loadException") { |
141 return new Future.error(new LoadException(path, response["message"])); | 185 return new Future.error(new LoadException(path, response["message"])); |
142 } else if (response["type"] == "error") { | 186 } else if (response["type"] == "error") { |
143 var asyncError = RemoteException.deserialize(response["error"]); | 187 var asyncError = RemoteException.deserialize(response["error"]); |
144 return new Future.error( | 188 return new Future.error( |
(...skipping 12 matching lines...) Expand all Loading... |
157 Future close() { | 201 Future close() { |
158 for (var isolate in _isolates) { | 202 for (var isolate in _isolates) { |
159 isolate.kill(); | 203 isolate.kill(); |
160 } | 204 } |
161 _isolates.clear(); | 205 _isolates.clear(); |
162 | 206 |
163 if (_browserServerCompleter == null) return new Future.value(); | 207 if (_browserServerCompleter == null) return new Future.value(); |
164 return _browserServer.then((browserServer) => browserServer.close()); | 208 return _browserServer.then((browserServer) => browserServer.close()); |
165 } | 209 } |
166 } | 210 } |
OLD | NEW |