OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS d.file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS d.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 pub_tests; | 5 library pub_tests; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:convert'; | 8 import 'dart:convert'; |
9 import 'dart:io'; | 9 import 'dart:io'; |
10 | 10 |
11 import 'package:http/http.dart' as http; | 11 import 'package:http/http.dart' as http; |
12 import 'package:path/path.dart' as p; | |
13 import 'package:scheduled_test/scheduled_process.dart'; | 12 import 'package:scheduled_test/scheduled_process.dart'; |
14 import 'package:scheduled_test/scheduled_stream.dart'; | 13 import 'package:scheduled_test/scheduled_stream.dart'; |
15 import 'package:scheduled_test/scheduled_test.dart'; | 14 import 'package:scheduled_test/scheduled_test.dart'; |
16 | 15 |
| 16 import '../../lib/src/utils.dart'; |
17 import '../descriptor.dart' as d; | 17 import '../descriptor.dart' as d; |
18 import '../test_pub.dart'; | 18 import '../test_pub.dart'; |
19 | 19 |
20 /// The pub process running "pub serve". | 20 /// The pub process running "pub serve". |
21 ScheduledProcess _pubServer; | 21 ScheduledProcess _pubServer; |
22 | 22 |
23 /// The ephemeral ports assigned to the running servers, associated with the | 23 /// The ephemeral ports assigned to the running servers, associated with the |
24 /// directories they're serving. | 24 /// directories they're serving. |
25 final _ports = new Map<String, int>(); | 25 final _ports = new Map<String, int>(); |
26 | 26 |
| 27 /// A completer that completes when the server has been started and the served |
| 28 /// ports are known. |
| 29 Completer _portsCompleter; |
| 30 |
27 /// The web socket connection to the running pub process, or `null` if no | 31 /// The web socket connection to the running pub process, or `null` if no |
28 /// connection has been made. | 32 /// connection has been made. |
29 WebSocket _webSocket; | 33 WebSocket _webSocket; |
30 Stream _webSocketBroadcastStream; | 34 Stream _webSocketBroadcastStream; |
31 | 35 |
32 /// The code for a transformer that renames ".txt" files to ".out" and adds a | 36 /// The code for a transformer that renames ".txt" files to ".out" and adds a |
33 /// ".out" suffix. | 37 /// ".out" suffix. |
34 const REWRITE_TRANSFORMER = """ | 38 const REWRITE_TRANSFORMER = """ |
35 import 'dart:async'; | 39 import 'dart:async'; |
36 | 40 |
(...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
122 /// | 126 /// |
123 /// If [shouldGetFirst] is `true`, validates that pub get is run first. | 127 /// If [shouldGetFirst] is `true`, validates that pub get is run first. |
124 /// | 128 /// |
125 /// If [createWebDir] is `true`, creates a `web/` directory if one doesn't exist | 129 /// If [createWebDir] is `true`, creates a `web/` directory if one doesn't exist |
126 /// so pub doesn't complain about having nothing to serve. | 130 /// so pub doesn't complain about having nothing to serve. |
127 /// | 131 /// |
128 /// Returns the `pub serve` process. | 132 /// Returns the `pub serve` process. |
129 ScheduledProcess pubServe({bool shouldGetFirst: false, bool createWebDir: true, | 133 ScheduledProcess pubServe({bool shouldGetFirst: false, bool createWebDir: true, |
130 Iterable<String> args}) { | 134 Iterable<String> args}) { |
131 _pubServer = startPubServe(args: args, createWebDir: createWebDir); | 135 _pubServer = startPubServe(args: args, createWebDir: createWebDir); |
| 136 _portsCompleter = new Completer(); |
132 | 137 |
133 currentSchedule.onComplete.schedule(() { | 138 currentSchedule.onComplete.schedule(() { |
| 139 _portsCompleter = null; |
134 _ports.clear(); | 140 _ports.clear(); |
135 | 141 |
136 if (_webSocket != null) { | 142 if (_webSocket != null) { |
137 _webSocket.close(); | 143 _webSocket.close(); |
138 _webSocket = null; | 144 _webSocket = null; |
139 _webSocketBroadcastStream = null; | 145 _webSocketBroadcastStream = null; |
140 } | 146 } |
141 }); | 147 }); |
142 | 148 |
143 if (shouldGetFirst) { | 149 if (shouldGetFirst) { |
144 _pubServer.stdout.expect(consumeThrough("Got dependencies!")); | 150 _pubServer.stdout.expect(consumeThrough("Got dependencies!")); |
145 } | 151 } |
146 | 152 |
147 // The server should emit one or more ports. | 153 // The server should emit one or more ports. |
148 _pubServer.stdout.expect( | 154 _pubServer.stdout.expect( |
149 consumeWhile(predicate(_parsePort, 'emits server url'))); | 155 consumeWhile(predicate(_parsePort, 'emits server url'))); |
150 schedule(() => expect(_ports, isNot(isEmpty))); | 156 schedule(() { |
| 157 expect(_ports, isNot(isEmpty)); |
| 158 _portsCompleter.complete(); |
| 159 }); |
151 | 160 |
152 return _pubServer; | 161 return _pubServer; |
153 } | 162 } |
154 | 163 |
155 /// The regular expression for parsing pub's output line describing the URL for | 164 /// The regular expression for parsing pub's output line describing the URL for |
156 /// the server. | 165 /// the server. |
157 final _parsePortRegExp = new RegExp(r"([^ ]+) +on http://127\.0\.0\.1:(\d+)"); | 166 final _parsePortRegExp = new RegExp(r"([^ ]+) +on http://127\.0\.0\.1:(\d+)"); |
158 | 167 |
159 /// Parses the port number from the "Serving blah on 127.0.0.1:1234" line | 168 /// Parses the port number from the "Serving blah on 127.0.0.1:1234" line |
160 /// printed by pub serve. | 169 /// printed by pub serve. |
161 bool _parsePort(String line) { | 170 bool _parsePort(String line) { |
162 var match = _parsePortRegExp.firstMatch(line); | 171 var match = _parsePortRegExp.firstMatch(line); |
163 if (match == null) return false; | 172 if (match == null) return false; |
164 _ports[match[1]] = int.parse(match[2]); | 173 _ports[match[1]] = int.parse(match[2]); |
165 return true; | 174 return true; |
166 } | 175 } |
167 | 176 |
168 void endPubServe() { | 177 void endPubServe() { |
169 _pubServer.kill(); | 178 _pubServer.kill(); |
170 } | 179 } |
171 | 180 |
172 /// Schedules an HTTP request to the running pub server with [urlPath] and | 181 /// Schedules an HTTP request to the running pub server with [urlPath] and |
173 /// verifies that it responds with a body that matches [expectation]. | 182 /// verifies that it responds with a body that matches [expectation]. |
174 /// | 183 /// |
175 /// [expectation] may either be a [Matcher] or a string to match an exact body. | 184 /// [expectation] may either be a [Matcher] or a string to match an exact body. |
176 /// [root] indicates which server should be accessed, and defaults to "web". | 185 /// [root] indicates which server should be accessed, and defaults to "web". |
177 /// [headers] may be either a [Matcher] or a map to match an exact headers map. | 186 /// [headers] may be either a [Matcher] or a map to match an exact headers map. |
178 void requestShouldSucceed(String urlPath, expectation, {String root, headers}) { | 187 void requestShouldSucceed(String urlPath, expectation, {String root, headers}) { |
179 schedule(() { | 188 schedule(() { |
180 return http.get("${_serverUrl(root)}/$urlPath").then((response) { | 189 return http.get(_getServerUrlSync(root, urlPath)).then((response) { |
181 if (expectation != null) expect(response.body, expectation); | 190 if (expectation != null) expect(response.body, expectation); |
182 if (headers != null) expect(response.headers, headers); | 191 if (headers != null) expect(response.headers, headers); |
183 }); | 192 }); |
184 }, "request $urlPath"); | 193 }, "request $urlPath"); |
185 } | 194 } |
186 | 195 |
187 /// Schedules an HTTP request to the running pub server with [urlPath] and | 196 /// Schedules an HTTP request to the running pub server with [urlPath] and |
188 /// verifies that it responds with a 404. | 197 /// verifies that it responds with a 404. |
189 /// | 198 /// |
190 /// [root] indicates which server should be accessed, and defaults to "web". | 199 /// [root] indicates which server should be accessed, and defaults to "web". |
191 void requestShould404(String urlPath, {String root}) { | 200 void requestShould404(String urlPath, {String root}) { |
192 schedule(() { | 201 schedule(() { |
193 return http.get("${_serverUrl(root)}/$urlPath").then((response) { | 202 return http.get(_getServerUrlSync(root, urlPath)).then((response) { |
194 expect(response.statusCode, equals(404)); | 203 expect(response.statusCode, equals(404)); |
195 }); | 204 }); |
196 }, "request $urlPath"); | 205 }, "request $urlPath"); |
197 } | 206 } |
198 | 207 |
199 /// Schedules an HTTP request to the running pub server with [urlPath] and | 208 /// Schedules an HTTP request to the running pub server with [urlPath] and |
200 /// verifies that it responds with a redirect to the given [redirectTarget]. | 209 /// verifies that it responds with a redirect to the given [redirectTarget]. |
201 /// | 210 /// |
202 /// [redirectTarget] may be either a [Matcher] or a string to match an exact | 211 /// [redirectTarget] may be either a [Matcher] or a string to match an exact |
203 /// URL. [root] indicates which server should be accessed, and defaults to | 212 /// URL. [root] indicates which server should be accessed, and defaults to |
204 /// "web". | 213 /// "web". |
205 void requestShouldRedirect(String urlPath, redirectTarget, {String root}) { | 214 void requestShouldRedirect(String urlPath, redirectTarget, {String root}) { |
206 schedule(() { | 215 schedule(() { |
207 var request = new http.Request("GET", | 216 var request = new http.Request("GET", |
208 Uri.parse("${_serverUrl(root)}/$urlPath")); | 217 Uri.parse(_getServerUrlSync(root, urlPath))); |
209 request.followRedirects = false; | 218 request.followRedirects = false; |
210 return request.send().then((response) { | 219 return request.send().then((response) { |
211 expect(response.statusCode ~/ 100, equals(3)); | 220 expect(response.statusCode ~/ 100, equals(3)); |
212 | 221 |
213 expect(response.headers, containsPair('location', redirectTarget)); | 222 expect(response.headers, containsPair('location', redirectTarget)); |
214 }); | 223 }); |
215 }, "request $urlPath"); | 224 }, "request $urlPath"); |
216 } | 225 } |
217 | 226 |
218 /// Schedules an HTTP POST to the running pub server with [urlPath] and verifies | 227 /// Schedules an HTTP POST to the running pub server with [urlPath] and verifies |
219 /// that it responds with a 405. | 228 /// that it responds with a 405. |
220 /// | 229 /// |
221 /// [root] indicates which server should be accessed, and defaults to "web". | 230 /// [root] indicates which server should be accessed, and defaults to "web". |
222 void postShould405(String urlPath, {String root}) { | 231 void postShould405(String urlPath, {String root}) { |
223 schedule(() { | 232 schedule(() { |
224 return http.post("${_serverUrl(root)}/$urlPath").then((response) { | 233 return http.post(_getServerUrlSync(root, urlPath)).then((response) { |
225 expect(response.statusCode, equals(405)); | 234 expect(response.statusCode, equals(405)); |
226 }); | 235 }); |
227 }, "request $urlPath"); | 236 }, "request $urlPath"); |
228 } | 237 } |
229 | 238 |
230 /// Reads lines from pub serve's stdout until it prints the build success | 239 /// Reads lines from pub serve's stdout until it prints the build success |
231 /// message. | 240 /// message. |
232 /// | 241 /// |
233 /// The schedule will not proceed until the output is found. If not found, it | 242 /// The schedule will not proceed until the output is found. If not found, it |
234 /// will eventually time out. | 243 /// will eventually time out. |
(...skipping 14 matching lines...) Expand all Loading... |
249 // server, use that port for the websocket interface. | 258 // server, use that port for the websocket interface. |
250 var port = _ports.values.first; | 259 var port = _ports.values.first; |
251 return WebSocket.connect("ws://127.0.0.1:$port").then((socket) { | 260 return WebSocket.connect("ws://127.0.0.1:$port").then((socket) { |
252 _webSocket = socket; | 261 _webSocket = socket; |
253 // TODO(rnystrom): Works around #13913. | 262 // TODO(rnystrom): Works around #13913. |
254 _webSocketBroadcastStream = _webSocket.asBroadcastStream(); | 263 _webSocketBroadcastStream = _webSocket.asBroadcastStream(); |
255 }); | 264 }); |
256 } | 265 } |
257 | 266 |
258 /// Sends [request] (an arbitrary JSON object) to the running pub serve's web | 267 /// Sends [request] (an arbitrary JSON object) to the running pub serve's web |
259 /// socket connection, waits for a reply, then verifies that the reply matches | 268 /// socket connection, waits for a reply, then verifies that the reply is |
260 /// [expectation]. | 269 /// either equal to [replyEquals] or matches [replyMatches]. |
| 270 /// |
| 271 /// Only one of [replyEquals] or [replyMatches] may be provided. |
| 272 /// |
| 273 /// [request] and [replyMatches] may contain futures, in which case this will |
| 274 /// wait until they've completed before matching. |
261 /// | 275 /// |
262 /// If [encodeRequest] is `false`, then [request] will be sent as-is over the | 276 /// If [encodeRequest] is `false`, then [request] will be sent as-is over the |
263 /// socket. It omitted, request is JSON encoded to a string first. | 277 /// socket. It omitted, request is JSON encoded to a string first. |
264 void webSocketShouldReply(request, expectation, {bool encodeRequest: true}) { | 278 void expectWebSocketCall(request, {Map replyEquals, replyMatches, |
| 279 bool encodeRequest: true}) { |
| 280 assert((replyEquals == null) != (replyMatches == null)); |
| 281 |
265 schedule(() => _ensureWebSocket().then((_) { | 282 schedule(() => _ensureWebSocket().then((_) { |
266 if (encodeRequest) request = JSON.encode(request); | 283 var matcherFuture; |
267 _webSocket.add(request); | 284 if (replyMatches != null) { |
268 return _webSocketBroadcastStream.first.then((value) { | 285 matcherFuture = new Future.value(replyMatches); |
269 expect(JSON.decode(value), expectation); | 286 } else { |
| 287 matcherFuture = awaitObject(replyEquals).then((reply) => equals(reply)); |
| 288 } |
| 289 |
| 290 return matcherFuture.then((matcher) { |
| 291 return awaitObject(request).then((completeRequest) { |
| 292 if (encodeRequest) completeRequest = JSON.encode(completeRequest); |
| 293 _webSocket.add(completeRequest); |
| 294 |
| 295 return _webSocketBroadcastStream.first.then((value) { |
| 296 expect(JSON.decode(value), matcher); |
| 297 }); |
| 298 }); |
270 }); | 299 }); |
271 }), "send $request to web socket and expect reply that $expectation"); | 300 }), "send $request to web socket and expect reply $replyEquals"); |
272 } | 301 } |
273 | 302 |
274 /// Returns the URL for the server serving from [root]. | 303 /// Returns a [Future] that completes to a URL string for the server serving |
275 String _serverUrl([String root]) { | 304 /// [path] from [root]. |
| 305 /// |
| 306 /// If [root] is omitted, defaults to "web". If [path] is omitted, no path is |
| 307 /// included. The Future will complete once the server is up and running and |
| 308 /// the bound ports are known. |
| 309 Future<String> getServerUrl([String root, String path]) => |
| 310 _portsCompleter.future.then((_) => _getServerUrlSync(root, path)); |
| 311 |
| 312 /// Returns a URL string for the server serving [path] from [root]. |
| 313 /// |
| 314 /// If [root] is omitted, defaults to "web". If [path] is omitted, no path is |
| 315 /// included. Unlike [getServerUrl], this should only be called after the ports |
| 316 /// are known. |
| 317 String _getServerUrlSync([String root, String path]) { |
276 if (root == null) root = 'web'; | 318 if (root == null) root = 'web'; |
277 expect(_ports, contains(root)); | 319 expect(_ports, contains(root)); |
278 return "http://127.0.0.1:${_ports[root]}"; | 320 var url = "http://127.0.0.1:${_ports[root]}"; |
279 } | 321 if (path != null) url = "$url/$path"; |
| 322 return url; |
| 323 } |
| 324 |
OLD | NEW |