OLD | NEW |
| 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 |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
1 library pub_tests; | 5 library pub_tests; |
| 6 |
2 import 'dart:async'; | 7 import 'dart:async'; |
3 import 'dart:convert'; | 8 import 'dart:convert'; |
4 import 'dart:io'; | 9 import 'dart:io'; |
| 10 |
5 import 'package:http/http.dart' as http; | 11 import 'package:http/http.dart' as http; |
6 import 'package:scheduled_test/scheduled_process.dart'; | 12 import 'package:scheduled_test/scheduled_process.dart'; |
7 import 'package:scheduled_test/scheduled_stream.dart'; | 13 import 'package:scheduled_test/scheduled_stream.dart'; |
8 import 'package:scheduled_test/scheduled_test.dart'; | 14 import 'package:scheduled_test/scheduled_test.dart'; |
9 import 'package:stack_trace/stack_trace.dart'; | 15 import 'package:stack_trace/stack_trace.dart'; |
| 16 |
10 import '../../lib/src/utils.dart'; | 17 import '../../lib/src/utils.dart'; |
11 import '../descriptor.dart' as d; | 18 import '../descriptor.dart' as d; |
12 import '../test_pub.dart'; | 19 import '../test_pub.dart'; |
| 20 |
| 21 /// The pub process running "pub serve". |
13 ScheduledProcess _pubServer; | 22 ScheduledProcess _pubServer; |
| 23 |
| 24 /// The ephemeral port assign to the running admin server. |
14 int _adminPort; | 25 int _adminPort; |
| 26 |
| 27 /// The ephemeral ports assigned to the running servers, associated with the |
| 28 /// directories they're serving. |
15 final _ports = new Map<String, int>(); | 29 final _ports = new Map<String, int>(); |
| 30 |
| 31 /// A completer that completes when the server has been started and the served |
| 32 /// ports are known. |
16 Completer _portsCompleter; | 33 Completer _portsCompleter; |
| 34 |
| 35 /// The web socket connection to the running pub process, or `null` if no |
| 36 /// connection has been made. |
17 WebSocket _webSocket; | 37 WebSocket _webSocket; |
18 Stream _webSocketBroadcastStream; | 38 Stream _webSocketBroadcastStream; |
| 39 |
| 40 /// The code for a transformer that renames ".txt" files to ".out" and adds a |
| 41 /// ".out" suffix. |
19 const REWRITE_TRANSFORMER = """ | 42 const REWRITE_TRANSFORMER = """ |
20 import 'dart:async'; | 43 import 'dart:async'; |
21 | 44 |
22 import 'package:barback/barback.dart'; | 45 import 'package:barback/barback.dart'; |
23 | 46 |
24 class RewriteTransformer extends Transformer { | 47 class RewriteTransformer extends Transformer { |
25 RewriteTransformer.asPlugin(); | 48 RewriteTransformer.asPlugin(); |
26 | 49 |
27 String get allowedExtensions => '.txt'; | 50 String get allowedExtensions => '.txt'; |
28 | 51 |
29 Future apply(Transform transform) { | 52 Future apply(Transform transform) { |
30 return transform.primaryInput.readAsString().then((contents) { | 53 return transform.primaryInput.readAsString().then((contents) { |
31 var id = transform.primaryInput.id.changeExtension(".out"); | 54 var id = transform.primaryInput.id.changeExtension(".out"); |
32 transform.addOutput(new Asset.fromString(id, "\$contents.out")); | 55 transform.addOutput(new Asset.fromString(id, "\$contents.out")); |
33 }); | 56 }); |
34 } | 57 } |
35 } | 58 } |
36 """; | 59 """; |
| 60 |
| 61 /// The code for a lazy version of [REWRITE_TRANSFORMER]. |
37 const LAZY_TRANSFORMER = """ | 62 const LAZY_TRANSFORMER = """ |
38 import 'dart:async'; | 63 import 'dart:async'; |
39 | 64 |
40 import 'package:barback/barback.dart'; | 65 import 'package:barback/barback.dart'; |
41 | 66 |
42 class LazyRewriteTransformer extends Transformer implements LazyTransformer { | 67 class LazyRewriteTransformer extends Transformer implements LazyTransformer { |
43 LazyRewriteTransformer.asPlugin(); | 68 LazyRewriteTransformer.asPlugin(); |
44 | 69 |
45 String get allowedExtensions => '.txt'; | 70 String get allowedExtensions => '.txt'; |
46 | 71 |
47 Future apply(Transform transform) { | 72 Future apply(Transform transform) { |
48 transform.logger.info('Rewriting \${transform.primaryInput.id}.'); | 73 transform.logger.info('Rewriting \${transform.primaryInput.id}.'); |
49 return transform.primaryInput.readAsString().then((contents) { | 74 return transform.primaryInput.readAsString().then((contents) { |
50 var id = transform.primaryInput.id.changeExtension(".out"); | 75 var id = transform.primaryInput.id.changeExtension(".out"); |
51 transform.addOutput(new Asset.fromString(id, "\$contents.out")); | 76 transform.addOutput(new Asset.fromString(id, "\$contents.out")); |
52 }); | 77 }); |
53 } | 78 } |
54 | 79 |
55 Future declareOutputs(DeclaringTransform transform) { | 80 Future declareOutputs(DeclaringTransform transform) { |
56 transform.declareOutput(transform.primaryId.changeExtension(".out")); | 81 transform.declareOutput(transform.primaryId.changeExtension(".out")); |
57 return new Future.value(); | 82 return new Future.value(); |
58 } | 83 } |
59 } | 84 } |
60 """; | 85 """; |
| 86 |
| 87 /// The web socket error code for a directory not being served. |
61 const NOT_SERVED = 1; | 88 const NOT_SERVED = 1; |
| 89 |
| 90 /// Returns the source code for a Dart library defining a Transformer that |
| 91 /// rewrites Dart files. |
| 92 /// |
| 93 /// The transformer defines a constant named TOKEN whose value is [id]. When the |
| 94 /// transformer transforms another Dart file, it will look for a "TOKEN" |
| 95 /// constant definition there and modify it to include *this* transformer's |
| 96 /// TOKEN value as well. |
| 97 /// |
| 98 /// If [import] is passed, it should be the name of a package that defines its |
| 99 /// own TOKEN constant. The primary library of that package will be imported |
| 100 /// here and its TOKEN value will be added to this library's. |
| 101 /// |
| 102 /// This transformer takes one configuration field: "addition". This is |
| 103 /// concatenated to its TOKEN value before adding it to the output library. |
62 String dartTransformer(String id, {String import}) { | 104 String dartTransformer(String id, {String import}) { |
63 if (import != null) { | 105 if (import != null) { |
64 id = '$id imports \${$import.TOKEN}'; | 106 id = '$id imports \${$import.TOKEN}'; |
65 import = 'import "package:$import/$import.dart" as $import;'; | 107 import = 'import "package:$import/$import.dart" as $import;'; |
66 } else { | 108 } else { |
67 import = ''; | 109 import = ''; |
68 } | 110 } |
| 111 |
69 return """ | 112 return """ |
70 import 'dart:async'; | 113 import 'dart:async'; |
71 | 114 |
72 import 'package:barback/barback.dart'; | 115 import 'package:barback/barback.dart'; |
73 $import | 116 $import |
74 | 117 |
75 import 'dart:io'; | 118 import 'dart:io'; |
76 | 119 |
77 const TOKEN = "$id"; | 120 const TOKEN = "$id"; |
78 | 121 |
(...skipping 13 matching lines...) Expand all Loading... |
92 var token = TOKEN; | 135 var token = TOKEN; |
93 var addition = _settings.configuration["addition"]; | 136 var addition = _settings.configuration["addition"]; |
94 if (addition != null) token += addition; | 137 if (addition != null) token += addition; |
95 return 'const TOKEN = "(\${match[1]}, \$token)";'; | 138 return 'const TOKEN = "(\${match[1]}, \$token)";'; |
96 }))); | 139 }))); |
97 }); | 140 }); |
98 } | 141 } |
99 } | 142 } |
100 """; | 143 """; |
101 } | 144 } |
| 145 |
| 146 /// Schedules starting the `pub serve` process. |
| 147 /// |
| 148 /// Unlike [pubServe], this doesn't determine the port number of the server, and |
| 149 /// so may be used to test for errors in the initialization process. |
| 150 /// |
| 151 /// Returns the `pub serve` process. |
102 ScheduledProcess startPubServe({Iterable<String> args, bool createWebDir: true}) | 152 ScheduledProcess startPubServe({Iterable<String> args, bool createWebDir: true}) |
103 { | 153 { |
104 var pubArgs = ["serve", "--port=0", "--force-poll", "--log-admin-url"]; | 154 var pubArgs = ["serve", "--port=0", // Use port 0 to get an ephemeral port. |
| 155 "--force-poll", "--log-admin-url"]; |
| 156 |
105 if (args != null) pubArgs.addAll(args); | 157 if (args != null) pubArgs.addAll(args); |
| 158 |
| 159 // Dart2js can take a long time to compile dart code, so we increase the |
| 160 // timeout to cope with that. |
106 currentSchedule.timeout *= 1.5; | 161 currentSchedule.timeout *= 1.5; |
| 162 |
107 if (createWebDir) d.dir(appPath, [d.dir("web")]).create(); | 163 if (createWebDir) d.dir(appPath, [d.dir("web")]).create(); |
108 return startPub(args: pubArgs); | 164 return startPub(args: pubArgs); |
109 } | 165 } |
| 166 |
| 167 /// Schedules starting the "pub serve" process and records its port number for |
| 168 /// future requests. |
| 169 /// |
| 170 /// If [shouldGetFirst] is `true`, validates that pub get is run first. |
| 171 /// |
| 172 /// If [createWebDir] is `true`, creates a `web/` directory if one doesn't exist |
| 173 /// so pub doesn't complain about having nothing to serve. |
| 174 /// |
| 175 /// Returns the `pub serve` process. |
110 ScheduledProcess pubServe({bool shouldGetFirst: false, bool createWebDir: true, | 176 ScheduledProcess pubServe({bool shouldGetFirst: false, bool createWebDir: true, |
111 Iterable<String> args}) { | 177 Iterable<String> args}) { |
112 _pubServer = startPubServe(args: args, createWebDir: createWebDir); | 178 _pubServer = startPubServe(args: args, createWebDir: createWebDir); |
113 _portsCompleter = new Completer(); | 179 _portsCompleter = new Completer(); |
| 180 |
114 currentSchedule.onComplete.schedule(() { | 181 currentSchedule.onComplete.schedule(() { |
115 _portsCompleter = null; | 182 _portsCompleter = null; |
116 _ports.clear(); | 183 _ports.clear(); |
| 184 |
117 if (_webSocket != null) { | 185 if (_webSocket != null) { |
118 _webSocket.close(); | 186 _webSocket.close(); |
119 _webSocket = null; | 187 _webSocket = null; |
120 _webSocketBroadcastStream = null; | 188 _webSocketBroadcastStream = null; |
121 } | 189 } |
122 }); | 190 }); |
| 191 |
123 if (shouldGetFirst) { | 192 if (shouldGetFirst) { |
124 _pubServer.stdout.expect( | 193 _pubServer.stdout.expect( |
125 consumeThrough( | 194 consumeThrough( |
126 anyOf(["Got dependencies!", matches(new RegExp(r"^Changed \d+ depend
enc"))]))); | 195 anyOf(["Got dependencies!", matches(new RegExp(r"^Changed \d+ depend
enc"))]))); |
127 } | 196 } |
| 197 |
128 _pubServer.stdout.expect(startsWith("Loading source assets...")); | 198 _pubServer.stdout.expect(startsWith("Loading source assets...")); |
129 _pubServer.stdout.expect(consumeWhile(matches("Loading .* transformers..."))); | 199 _pubServer.stdout.expect(consumeWhile(matches("Loading .* transformers..."))); |
| 200 |
130 _pubServer.stdout.expect(predicate(_parseAdminPort)); | 201 _pubServer.stdout.expect(predicate(_parseAdminPort)); |
| 202 |
| 203 // The server should emit one or more ports. |
131 _pubServer.stdout.expect( | 204 _pubServer.stdout.expect( |
132 consumeWhile(predicate(_parsePort, 'emits server url'))); | 205 consumeWhile(predicate(_parsePort, 'emits server url'))); |
133 schedule(() { | 206 schedule(() { |
134 expect(_ports, isNot(isEmpty)); | 207 expect(_ports, isNot(isEmpty)); |
135 _portsCompleter.complete(); | 208 _portsCompleter.complete(); |
136 }); | 209 }); |
| 210 |
137 return _pubServer; | 211 return _pubServer; |
138 } | 212 } |
| 213 |
| 214 /// The regular expression for parsing pub's output line describing the URL for |
| 215 /// the server. |
139 final _parsePortRegExp = new RegExp(r"([^ ]+) +on http://localhost:(\d+)"); | 216 final _parsePortRegExp = new RegExp(r"([^ ]+) +on http://localhost:(\d+)"); |
| 217 |
| 218 /// Parses the port number from the "Running admin server on localhost:1234" |
| 219 /// line printed by pub serve. |
140 bool _parseAdminPort(String line) { | 220 bool _parseAdminPort(String line) { |
141 var match = _parsePortRegExp.firstMatch(line); | 221 var match = _parsePortRegExp.firstMatch(line); |
142 if (match == null) return false; | 222 if (match == null) return false; |
143 _adminPort = int.parse(match[2]); | 223 _adminPort = int.parse(match[2]); |
144 return true; | 224 return true; |
145 } | 225 } |
| 226 |
| 227 /// Parses the port number from the "Serving blah on localhost:1234" line |
| 228 /// printed by pub serve. |
146 bool _parsePort(String line) { | 229 bool _parsePort(String line) { |
147 var match = _parsePortRegExp.firstMatch(line); | 230 var match = _parsePortRegExp.firstMatch(line); |
148 if (match == null) return false; | 231 if (match == null) return false; |
149 _ports[match[1]] = int.parse(match[2]); | 232 _ports[match[1]] = int.parse(match[2]); |
150 return true; | 233 return true; |
151 } | 234 } |
| 235 |
152 void endPubServe() { | 236 void endPubServe() { |
153 _pubServer.kill(); | 237 _pubServer.kill(); |
154 } | 238 } |
| 239 |
| 240 /// Schedules an HTTP request to the running pub server with [urlPath] and |
| 241 /// invokes [callback] with the response. |
| 242 /// |
| 243 /// [root] indicates which server should be accessed, and defaults to "web". |
155 Future<http.Response> scheduleRequest(String urlPath, {String root}) { | 244 Future<http.Response> scheduleRequest(String urlPath, {String root}) { |
156 return schedule(() { | 245 return schedule(() { |
157 return http.get(_getServerUrlSync(root, urlPath)); | 246 return http.get(_getServerUrlSync(root, urlPath)); |
158 }, "request $urlPath"); | 247 }, "request $urlPath"); |
159 } | 248 } |
| 249 |
| 250 /// Schedules an HTTP request to the running pub server with [urlPath] and |
| 251 /// verifies that it responds with a body that matches [expectation]. |
| 252 /// |
| 253 /// [expectation] may either be a [Matcher] or a string to match an exact body. |
| 254 /// [root] indicates which server should be accessed, and defaults to "web". |
| 255 /// [headers] may be either a [Matcher] or a map to match an exact headers map. |
160 void requestShouldSucceed(String urlPath, expectation, {String root, headers}) { | 256 void requestShouldSucceed(String urlPath, expectation, {String root, headers}) { |
161 scheduleRequest(urlPath, root: root).then((response) { | 257 scheduleRequest(urlPath, root: root).then((response) { |
162 if (expectation != null) expect(response.body, expectation); | 258 if (expectation != null) expect(response.body, expectation); |
163 if (headers != null) expect(response.headers, headers); | 259 if (headers != null) expect(response.headers, headers); |
164 }); | 260 }); |
165 } | 261 } |
| 262 |
| 263 /// Schedules an HTTP request to the running pub server with [urlPath] and |
| 264 /// verifies that it responds with a 404. |
| 265 /// |
| 266 /// [root] indicates which server should be accessed, and defaults to "web". |
166 void requestShould404(String urlPath, {String root}) { | 267 void requestShould404(String urlPath, {String root}) { |
167 scheduleRequest(urlPath, root: root).then((response) { | 268 scheduleRequest(urlPath, root: root).then((response) { |
168 expect(response.statusCode, equals(404)); | 269 expect(response.statusCode, equals(404)); |
169 }); | 270 }); |
170 } | 271 } |
| 272 |
| 273 /// Schedules an HTTP request to the running pub server with [urlPath] and |
| 274 /// verifies that it responds with a redirect to the given [redirectTarget]. |
| 275 /// |
| 276 /// [redirectTarget] may be either a [Matcher] or a string to match an exact |
| 277 /// URL. [root] indicates which server should be accessed, and defaults to |
| 278 /// "web". |
171 void requestShouldRedirect(String urlPath, redirectTarget, {String root}) { | 279 void requestShouldRedirect(String urlPath, redirectTarget, {String root}) { |
172 schedule(() { | 280 schedule(() { |
173 var request = | 281 var request = |
174 new http.Request("GET", Uri.parse(_getServerUrlSync(root, urlPath))); | 282 new http.Request("GET", Uri.parse(_getServerUrlSync(root, urlPath))); |
175 request.followRedirects = false; | 283 request.followRedirects = false; |
176 return request.send().then((response) { | 284 return request.send().then((response) { |
177 expect(response.statusCode ~/ 100, equals(3)); | 285 expect(response.statusCode ~/ 100, equals(3)); |
178 expect(response.headers, containsPair('location', redirectTarget)); | 286 expect(response.headers, containsPair('location', redirectTarget)); |
179 }); | 287 }); |
180 }, "request $urlPath"); | 288 }, "request $urlPath"); |
181 } | 289 } |
| 290 |
| 291 /// Schedules an HTTP POST to the running pub server with [urlPath] and verifies |
| 292 /// that it responds with a 405. |
| 293 /// |
| 294 /// [root] indicates which server should be accessed, and defaults to "web". |
182 void postShould405(String urlPath, {String root}) { | 295 void postShould405(String urlPath, {String root}) { |
183 schedule(() { | 296 schedule(() { |
184 return http.post(_getServerUrlSync(root, urlPath)).then((response) { | 297 return http.post(_getServerUrlSync(root, urlPath)).then((response) { |
185 expect(response.statusCode, equals(405)); | 298 expect(response.statusCode, equals(405)); |
186 }); | 299 }); |
187 }, "request $urlPath"); | 300 }, "request $urlPath"); |
188 } | 301 } |
| 302 |
| 303 /// Schedules an HTTP request to the (theoretically) running pub server with |
| 304 /// [urlPath] and verifies that it cannot be connected to. |
| 305 /// |
| 306 /// [root] indicates which server should be accessed, and defaults to "web". |
189 void requestShouldNotConnect(String urlPath, {String root}) { | 307 void requestShouldNotConnect(String urlPath, {String root}) { |
190 schedule(() { | 308 schedule(() { |
191 return expect( | 309 return expect( |
192 http.get(_getServerUrlSync(root, urlPath)), | 310 http.get(_getServerUrlSync(root, urlPath)), |
193 throwsA(new isInstanceOf<SocketException>())); | 311 throwsA(new isInstanceOf<SocketException>())); |
194 }, "request $urlPath"); | 312 }, "request $urlPath"); |
195 } | 313 } |
| 314 |
| 315 /// Reads lines from pub serve's stdout until it prints the build success |
| 316 /// message. |
| 317 /// |
| 318 /// The schedule will not proceed until the output is found. If not found, it |
| 319 /// will eventually time out. |
196 void waitForBuildSuccess() => | 320 void waitForBuildSuccess() => |
197 _pubServer.stdout.expect(consumeThrough(contains("successfully"))); | 321 _pubServer.stdout.expect(consumeThrough(contains("successfully"))); |
| 322 |
| 323 /// Schedules opening a web socket connection to the currently running pub |
| 324 /// serve. |
198 Future _ensureWebSocket() { | 325 Future _ensureWebSocket() { |
| 326 // Use the existing one if already connected. |
199 if (_webSocket != null) return new Future.value(); | 327 if (_webSocket != null) return new Future.value(); |
| 328 |
| 329 // Server should already be running. |
200 expect(_pubServer, isNotNull); | 330 expect(_pubServer, isNotNull); |
201 expect(_adminPort, isNotNull); | 331 expect(_adminPort, isNotNull); |
| 332 |
202 return WebSocket.connect("ws://localhost:$_adminPort").then((socket) { | 333 return WebSocket.connect("ws://localhost:$_adminPort").then((socket) { |
203 _webSocket = socket; | 334 _webSocket = socket; |
| 335 // TODO(rnystrom): Works around #13913. |
204 _webSocketBroadcastStream = _webSocket.map(JSON.decode).asBroadcastStream(); | 336 _webSocketBroadcastStream = _webSocket.map(JSON.decode).asBroadcastStream(); |
205 }); | 337 }); |
206 } | 338 } |
| 339 |
| 340 /// Schedules closing the web socket connection to the currently-running pub |
| 341 /// serve. |
207 void closeWebSocket() { | 342 void closeWebSocket() { |
208 schedule(() { | 343 schedule(() { |
209 return _ensureWebSocket().then((_) => _webSocket.close()).then((_) => _webSo
cket = | 344 return _ensureWebSocket().then((_) => _webSocket.close()).then((_) => _webSo
cket = |
210 null); | 345 null); |
211 }, "closing web socket"); | 346 }, "closing web socket"); |
212 } | 347 } |
| 348 |
| 349 /// Sends a JSON RPC 2.0 request to the running pub serve's web socket |
| 350 /// connection. |
| 351 /// |
| 352 /// This calls a method named [method] with the given [params] (or no |
| 353 /// parameters, if it's not passed). [params] may contain Futures, in which case |
| 354 /// this will wait until they've completed before sending the request. |
| 355 /// |
| 356 /// This schedules the request, but doesn't block the schedule on the response. |
| 357 /// It returns the response as a [Future]. |
213 Future<Map> webSocketRequest(String method, [Map params]) { | 358 Future<Map> webSocketRequest(String method, [Map params]) { |
214 var completer = new Completer(); | 359 var completer = new Completer(); |
215 schedule(() { | 360 schedule(() { |
216 return Future.wait( | 361 return Future.wait( |
217 [_ensureWebSocket(), awaitObject(params)]).then((results) { | 362 [_ensureWebSocket(), awaitObject(params),]).then((results) { |
218 var resolvedParams = results[1]; | 363 var resolvedParams = results[1]; |
219 chainToCompleter( | 364 chainToCompleter( |
220 currentSchedule.wrapFuture(_jsonRpcRequest(method, resolvedParams)), | 365 currentSchedule.wrapFuture(_jsonRpcRequest(method, resolvedParams)), |
221 completer); | 366 completer); |
222 }); | 367 }); |
223 }, "send $method with $params to web socket"); | 368 }, "send $method with $params to web socket"); |
224 return completer.future; | 369 return completer.future; |
225 } | 370 } |
| 371 |
| 372 /// Sends a JSON RPC 2.0 request to the running pub serve's web socket |
| 373 /// connection, waits for a reply, then verifies the result. |
| 374 /// |
| 375 /// This calls a method named [method] with the given [params]. [params] may |
| 376 /// contain Futures, in which case this will wait until they've completed before |
| 377 /// sending the request. |
| 378 /// |
| 379 /// The result is validated using [result], which may be a [Matcher] or a [Map] |
| 380 /// containing [Matcher]s and [Future]s. This will wait until any futures are |
| 381 /// completed before sending the request. |
| 382 /// |
| 383 /// Returns a [Future] that completes to the call's result. |
226 Future<Map> expectWebSocketResult(String method, Map params, result) { | 384 Future<Map> expectWebSocketResult(String method, Map params, result) { |
227 return schedule(() { | 385 return schedule(() { |
228 return Future.wait( | 386 return Future.wait( |
229 [webSocketRequest(method, params), awaitObject(result)]).then((results)
{ | 387 [webSocketRequest(method, params), awaitObject(result)]).then((results)
{ |
230 var response = results[0]; | 388 var response = results[0]; |
231 var resolvedResult = results[1]; | 389 var resolvedResult = results[1]; |
232 expect(response["result"], resolvedResult); | 390 expect(response["result"], resolvedResult); |
233 return response["result"]; | 391 return response["result"]; |
234 }); | 392 }); |
235 }, "send $method with $params to web socket and expect $result"); | 393 }, "send $method with $params to web socket and expect $result"); |
236 } | 394 } |
| 395 |
| 396 /// Sends a JSON RPC 2.0 request to the running pub serve's web socket |
| 397 /// connection, waits for a reply, then verifies the error response. |
| 398 /// |
| 399 /// This calls a method named [method] with the given [params]. [params] may |
| 400 /// contain Futures, in which case this will wait until they've completed before |
| 401 /// sending the request. |
| 402 /// |
| 403 /// The error response is validated using [errorCode] and [errorMessage]. Both |
| 404 /// of these must be provided. The error code is checked against [errorCode] and |
| 405 /// the error message is checked against [errorMessage]. Either of these may be |
| 406 /// matchers. |
| 407 /// |
| 408 /// If [data] is provided, it is a JSON value or matcher used to validate the |
| 409 /// "data" value of the error response. |
| 410 /// |
| 411 /// Returns a [Future] that completes to the error's [data] field. |
237 Future expectWebSocketError(String method, Map params, errorCode, errorMessage, | 412 Future expectWebSocketError(String method, Map params, errorCode, errorMessage, |
238 {data}) { | 413 {data}) { |
239 return schedule(() { | 414 return schedule(() { |
240 return webSocketRequest(method, params).then((response) { | 415 return webSocketRequest(method, params).then((response) { |
241 expect(response["error"]["code"], errorCode); | 416 expect(response["error"]["code"], errorCode); |
242 expect(response["error"]["message"], errorMessage); | 417 expect(response["error"]["message"], errorMessage); |
| 418 |
243 if (data != null) { | 419 if (data != null) { |
244 expect(response["error"]["data"], data); | 420 expect(response["error"]["data"], data); |
245 } | 421 } |
| 422 |
246 return response["error"]["data"]; | 423 return response["error"]["data"]; |
247 }); | 424 }); |
248 }, "send $method with $params to web socket and expect error $errorCode"); | 425 }, "send $method with $params to web socket and expect error $errorCode"); |
249 } | 426 } |
| 427 |
| 428 /// Validates that [root] was not bound to a port when pub serve started. |
250 Future expectNotServed(String root) { | 429 Future expectNotServed(String root) { |
251 return schedule(() { | 430 return schedule(() { |
252 expect(_ports.containsKey(root), isFalse); | 431 expect(_ports.containsKey(root), isFalse); |
253 }); | 432 }); |
254 } | 433 } |
| 434 |
| 435 /// The next id to use for a JSON-RPC 2.0 request. |
255 var _rpcId = 0; | 436 var _rpcId = 0; |
| 437 |
| 438 /// Sends a JSON-RPC 2.0 request calling [method] with [params]. |
| 439 /// |
| 440 /// Returns the response object. |
256 Future<Map> _jsonRpcRequest(String method, [Map params]) { | 441 Future<Map> _jsonRpcRequest(String method, [Map params]) { |
257 var id = _rpcId++; | 442 var id = _rpcId++; |
258 var message = { | 443 var message = { |
259 "jsonrpc": "2.0", | 444 "jsonrpc": "2.0", |
260 "method": method, | 445 "method": method, |
261 "id": id | 446 "id": id |
262 }; | 447 }; |
263 if (params != null) message["params"] = params; | 448 if (params != null) message["params"] = params; |
264 _webSocket.add(JSON.encode(message)); | 449 _webSocket.add(JSON.encode(message)); |
| 450 |
265 return _webSocketBroadcastStream.firstWhere( | 451 return _webSocketBroadcastStream.firstWhere( |
266 (response) => response["id"] == id).then((value) { | 452 (response) => response["id"] == id).then((value) { |
267 currentSchedule.addDebugInfo( | 453 currentSchedule.addDebugInfo( |
268 "Web Socket request $method with params $params\n" "Result: $value"); | 454 "Web Socket request $method with params $params\n" "Result: $value"); |
| 455 |
269 expect(value["id"], equals(id)); | 456 expect(value["id"], equals(id)); |
270 return value; | 457 return value; |
271 }); | 458 }); |
272 } | 459 } |
| 460 |
| 461 /// Returns a [Future] that completes to a URL string for the server serving |
| 462 /// [path] from [root]. |
| 463 /// |
| 464 /// If [root] is omitted, defaults to "web". If [path] is omitted, no path is |
| 465 /// included. The Future will complete once the server is up and running and |
| 466 /// the bound ports are known. |
273 Future<String> getServerUrl([String root, String path]) => | 467 Future<String> getServerUrl([String root, String path]) => |
274 _portsCompleter.future.then((_) => _getServerUrlSync(root, path)); | 468 _portsCompleter.future.then((_) => _getServerUrlSync(root, path)); |
| 469 |
| 470 /// Records that [root] has been bound to [port]. |
| 471 /// |
| 472 /// Used for testing the Web Socket API for binding new root directories to |
| 473 /// ports after pub serve has been started. |
275 registerServerPort(String root, int port) { | 474 registerServerPort(String root, int port) { |
276 _ports[root] = port; | 475 _ports[root] = port; |
277 } | 476 } |
| 477 |
| 478 /// Returns a URL string for the server serving [path] from [root]. |
| 479 /// |
| 480 /// If [root] is omitted, defaults to "web". If [path] is omitted, no path is |
| 481 /// included. Unlike [getServerUrl], this should only be called after the ports |
| 482 /// are known. |
278 String _getServerUrlSync([String root, String path]) { | 483 String _getServerUrlSync([String root, String path]) { |
279 if (root == null) root = 'web'; | 484 if (root == null) root = 'web'; |
280 expect(_ports, contains(root)); | 485 expect(_ports, contains(root)); |
281 var url = "http://localhost:${_ports[root]}"; | 486 var url = "http://localhost:${_ports[root]}"; |
282 if (path != null) url = "$url/$path"; | 487 if (path != null) url = "$url/$path"; |
283 return url; | 488 return url; |
284 } | 489 } |
| 490 |
OLD | NEW |