OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, 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 /// Test infrastructure for testing pub. Unlike typical unit tests, most pub | 5 /// Test infrastructure for testing pub. Unlike typical unit tests, most pub |
6 /// tests are integration tests that stage some stuff on the file system, run | 6 /// tests are integration tests that stage some stuff on the file system, run |
7 /// pub, and then validate the results. This library provides an API to build | 7 /// pub, and then validate the results. This library provides an API to build |
8 /// tests like that. | 8 /// tests like that. |
9 library test_pub; | 9 library test_pub; |
10 | 10 |
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
95 | 95 |
96 /// Creates an HTTP server to serve [contents] as static files. This server will | 96 /// Creates an HTTP server to serve [contents] as static files. This server will |
97 /// exist only for the duration of the pub run. | 97 /// exist only for the duration of the pub run. |
98 /// | 98 /// |
99 /// Subsequent calls to [serve] will replace the previous server. | 99 /// Subsequent calls to [serve] will replace the previous server. |
100 void serve([List<Descriptor> contents]) { | 100 void serve([List<Descriptor> contents]) { |
101 var baseDir = dir("serve-dir", contents); | 101 var baseDir = dir("serve-dir", contents); |
102 | 102 |
103 _schedule((_) { | 103 _schedule((_) { |
104 return _closeServer().then((_) { | 104 return _closeServer().then((_) { |
105 _server = new HttpServer(); | 105 return HttpServer.bind("127.0.0.1", 0).then((server) { |
106 _server.defaultRequestHandler = (request, response) { | 106 _server = server; |
107 var path = request.uri.replaceFirst("/", "").split("/"); | 107 server.listen((request) { |
108 response.persistentConnection = false; | 108 var response = request.response; |
109 var stream; | 109 var path = request.uri.path.replaceFirst("/", "").split("/"); |
110 try { | 110 response.persistentConnection = false; |
111 stream = baseDir.load(path); | 111 var stream; |
112 } catch (e) { | 112 try { |
113 response.statusCode = 404; | 113 stream = baseDir.load(path); |
114 response.contentLength = 0; | 114 } catch (e) { |
115 response.outputStream.close(); | 115 response.statusCode = 404; |
116 return; | 116 response.contentLength = 0; |
117 } | 117 response.close(); |
| 118 return; |
| 119 } |
118 | 120 |
119 stream.toBytes().then((data) { | 121 stream.toBytes().then((data) { |
120 response.statusCode = 200; | 122 response.statusCode = 200; |
121 response.contentLength = data.length; | 123 response.contentLength = data.length; |
122 response.outputStream.write(data); | 124 response.add(data); |
123 response.outputStream.close(); | 125 response.close(); |
124 }).catchError((e) { | 126 }).catchError((e) { |
125 print("Exception while handling ${request.uri}: $e"); | 127 print("Exception while handling ${request.uri}: $e"); |
126 response.statusCode = 500; | 128 response.statusCode = 500; |
127 response.reasonPhrase = e.message; | 129 response.reasonPhrase = e.message; |
128 response.outputStream.close(); | 130 response.close(); |
| 131 }); |
129 }); | 132 }); |
130 }; | 133 _portCompleter.complete(_server.port); |
131 _server.listen("127.0.0.1", 0); | 134 _scheduleCleanup((_) => _closeServer()); |
132 _portCompleter.complete(_server.port); | 135 return null; |
133 _scheduleCleanup((_) => _closeServer()); | 136 }); |
134 return null; | |
135 }); | 137 }); |
136 }); | 138 }); |
137 } | 139 } |
138 | 140 |
139 /// Closes [_server]. Returns a [Future] that will complete after the [_server] | 141 /// Closes [_server]. Returns a [Future] that will complete after the [_server] |
140 /// is closed. | 142 /// is closed. |
141 Future _closeServer() { | 143 Future _closeServer() { |
142 if (_server == null) return new Future.immediate(null); | 144 if (_server == null) return new Future.immediate(null); |
143 _server.close(); | 145 _server.close(); |
144 _server = null; | 146 _server = null; |
(...skipping 315 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
460 | 462 |
461 /// The path of the packages directory in the mock app used for tests. Relative | 463 /// The path of the packages directory in the mock app used for tests. Relative |
462 /// to the sandbox directory. | 464 /// to the sandbox directory. |
463 final String packagesPath = "$appPath/packages"; | 465 final String packagesPath = "$appPath/packages"; |
464 | 466 |
465 /// The type for callbacks that will be fired during [schedulePub]. Takes the | 467 /// The type for callbacks that will be fired during [schedulePub]. Takes the |
466 /// sandbox directory as a parameter. | 468 /// sandbox directory as a parameter. |
467 typedef Future _ScheduledEvent(String parentDir); | 469 typedef Future _ScheduledEvent(String parentDir); |
468 | 470 |
469 /// The list of events that are scheduled to run as part of the test case. | 471 /// The list of events that are scheduled to run as part of the test case. |
470 List<_ScheduledEvent> _scheduled; | 472 Queue<_ScheduledEvent> _scheduled; |
471 | 473 |
472 /// The list of events that are scheduled to run after the test case, even if | 474 /// The list of events that are scheduled to run after the test case, even if |
473 /// it failed. | 475 /// it failed. |
474 List<_ScheduledEvent> _scheduledCleanup; | 476 Queue<_ScheduledEvent> _scheduledCleanup; |
475 | 477 |
476 /// The list of events that are scheduled to run after the test case only if it | 478 /// The list of events that are scheduled to run after the test case only if it |
477 /// failed. | 479 /// failed. |
478 List<_ScheduledEvent> _scheduledOnException; | 480 Queue<_ScheduledEvent> _scheduledOnException; |
479 | 481 |
480 /// Set to true when the current batch of scheduled events should be aborted. | 482 /// Set to true when the current batch of scheduled events should be aborted. |
481 bool _abortScheduled = false; | 483 bool _abortScheduled = false; |
482 | 484 |
483 /// The time (in milliseconds) to wait for the entire scheduled test to | 485 /// The time (in milliseconds) to wait for the entire scheduled test to |
484 /// complete. | 486 /// complete. |
485 final _TIMEOUT = 30000; | 487 final _TIMEOUT = 30000; |
486 | 488 |
487 /// Defines an integration test. The [body] should schedule a series of | 489 /// Defines an integration test. The [body] should schedule a series of |
488 /// operations which will be run asynchronously. | 490 /// operations which will be run asynchronously. |
(...skipping 208 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
697 /// Note that this will only affect HTTP requests made via http.dart in the | 699 /// Note that this will only affect HTTP requests made via http.dart in the |
698 /// parent process. | 700 /// parent process. |
699 void useMockClient(MockClient client) { | 701 void useMockClient(MockClient client) { |
700 var oldInnerClient = httpClient.inner; | 702 var oldInnerClient = httpClient.inner; |
701 httpClient.inner = client; | 703 httpClient.inner = client; |
702 _scheduleCleanup((_) { | 704 _scheduleCleanup((_) { |
703 httpClient.inner = oldInnerClient; | 705 httpClient.inner = oldInnerClient; |
704 }); | 706 }); |
705 } | 707 } |
706 | 708 |
707 Future _runScheduled(List<_ScheduledEvent> scheduled) { | 709 Future _runScheduled(Queue<_ScheduledEvent> scheduled) { |
708 if (scheduled == null) return new Future.immediate(null); | 710 if (scheduled == null) return new Future.immediate(null); |
709 var iterator = scheduled.iterator; | |
710 | 711 |
711 Future runNextEvent(_) { | 712 Future runNextEvent(_) { |
712 if (_abortScheduled || !iterator.moveNext()) { | 713 if (_abortScheduled || scheduled.isEmpty) { |
713 _abortScheduled = false; | 714 _abortScheduled = false; |
714 scheduled.clear(); | |
715 return new Future.immediate(null); | 715 return new Future.immediate(null); |
716 } | 716 } |
717 | 717 |
718 var future = iterator.current(_sandboxDir); | 718 var future = scheduled.removeFirst()(_sandboxDir); |
719 if (future != null) { | 719 if (future != null) { |
720 return future.then(runNextEvent); | 720 return future.then(runNextEvent); |
721 } else { | 721 } else { |
722 return runNextEvent(null); | 722 return runNextEvent(null); |
723 } | 723 } |
724 } | 724 } |
725 | 725 |
726 return runNextEvent(null); | 726 return runNextEvent(null); |
727 } | 727 } |
728 | 728 |
(...skipping 419 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1148 /// Loads the contents of this tar file. | 1148 /// Loads the contents of this tar file. |
1149 ByteStream load(List<String> path) { | 1149 ByteStream load(List<String> path) { |
1150 if (!path.isEmpty) { | 1150 if (!path.isEmpty) { |
1151 throw "Can't load ${path.join('/')} from within $name: not a directory."; | 1151 throw "Can't load ${path.join('/')} from within $name: not a directory."; |
1152 } | 1152 } |
1153 | 1153 |
1154 var controller = new StreamController<List<int>>(); | 1154 var controller = new StreamController<List<int>>(); |
1155 // TODO(nweiz): propagate any errors to the return value. See issue 3657. | 1155 // TODO(nweiz): propagate any errors to the return value. See issue 3657. |
1156 withTempDir((tempDir) { | 1156 withTempDir((tempDir) { |
1157 return create(tempDir).then((tar) { | 1157 return create(tempDir).then((tar) { |
1158 var sourceStream = new File(tar).openInputStream(); | 1158 var sourceStream = new File(tar).openRead(); |
1159 return store(wrapInputStream(sourceStream), controller); | 1159 return store(sourceStream, controller); |
1160 }); | 1160 }); |
1161 }); | 1161 }); |
1162 return new ByteStream(controller.stream); | 1162 return new ByteStream(controller.stream); |
1163 } | 1163 } |
1164 } | 1164 } |
1165 | 1165 |
1166 /// A descriptor that validates that no file exists with the given name. | 1166 /// A descriptor that validates that no file exists with the given name. |
1167 class NothingDescriptor extends Descriptor { | 1167 class NothingDescriptor extends Descriptor { |
1168 NothingDescriptor(String name) : super(name); | 1168 NothingDescriptor(String name) : super(name); |
1169 | 1169 |
(...skipping 323 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1493 /// The requests to be ignored. | 1493 /// The requests to be ignored. |
1494 final _ignored = new Set<Pair<String, String>>(); | 1494 final _ignored = new Set<Pair<String, String>>(); |
1495 | 1495 |
1496 ScheduledServer._(this._server); | 1496 ScheduledServer._(this._server); |
1497 | 1497 |
1498 /// Creates a new server listening on an automatically-allocated port on | 1498 /// Creates a new server listening on an automatically-allocated port on |
1499 /// localhost. | 1499 /// localhost. |
1500 factory ScheduledServer() { | 1500 factory ScheduledServer() { |
1501 var scheduledServer; | 1501 var scheduledServer; |
1502 scheduledServer = new ScheduledServer._(_scheduleValue((_) { | 1502 scheduledServer = new ScheduledServer._(_scheduleValue((_) { |
1503 var server = new HttpServer(); | 1503 return HttpServer.bind("127.0.0.1", 0).then((server) { |
1504 server.defaultRequestHandler = scheduledServer._awaitHandle; | 1504 server.listen(scheduledServer._awaitHandle); |
1505 server.listen("127.0.0.1", 0); | 1505 _scheduleCleanup((_) => server.close()); |
1506 _scheduleCleanup((_) => server.close()); | 1506 return server; |
1507 return new Future.immediate(server); | 1507 }); |
1508 })); | 1508 })); |
1509 return scheduledServer; | 1509 return scheduledServer; |
1510 } | 1510 } |
1511 | 1511 |
1512 /// The port on which the server is listening. | 1512 /// The port on which the server is listening. |
1513 Future<int> get port => _server.then((s) => s.port); | 1513 Future<int> get port => _server.then((s) => s.port); |
1514 | 1514 |
1515 /// The base URL of the server, including its port. | 1515 /// The base URL of the server, including its port. |
1516 Future<Uri> get url => | 1516 Future<Uri> get url => |
1517 port.then((p) => Uri.parse("http://localhost:$p")); | 1517 port.then((p) => Uri.parse("http://localhost:$p")); |
1518 | 1518 |
1519 /// Assert that the next request has the given [method] and [path], and pass | 1519 /// Assert that the next request has the given [method] and [path], and pass |
1520 /// it to [handler] to handle. If [handler] returns a [Future], wait until | 1520 /// it to [handler] to handle. If [handler] returns a [Future], wait until |
1521 /// it's completed to continue the schedule. | 1521 /// it's completed to continue the schedule. |
1522 void handle(String method, String path, | 1522 void handle(String method, String path, |
1523 Future handler(HttpRequest request, HttpResponse response)) { | 1523 Future handler(HttpRequest request, HttpResponse response)) { |
1524 var handlerCompleter = new Completer<Function>(); | 1524 var handlerCompleter = new Completer<Function>(); |
1525 _scheduleValue((_) { | 1525 _scheduleValue((_) { |
1526 var requestCompleteCompleter = new Completer(); | 1526 var requestCompleteCompleter = new Completer(); |
1527 handlerCompleter.complete((request, response) { | 1527 handlerCompleter.complete((request, response) { |
1528 expect(request.method, equals(method)); | 1528 expect(request.method, equals(method)); |
1529 // TODO(nweiz): Use request.path once issue 7464 is fixed. | 1529 expect(request.uri.path, equals(path)); |
1530 expect(Uri.parse(request.uri).path, equals(path)); | |
1531 | 1530 |
1532 var future = handler(request, response); | 1531 var future = handler(request, response); |
1533 if (future == null) future = new Future.immediate(null); | 1532 if (future == null) future = new Future.immediate(null); |
1534 chainToCompleter(future, requestCompleteCompleter); | 1533 chainToCompleter(future, requestCompleteCompleter); |
1535 }); | 1534 }); |
1536 return timeout(requestCompleteCompleter.future, | 1535 return timeout(requestCompleteCompleter.future, |
1537 _SCHEDULE_TIMEOUT, "waiting for $method $path"); | 1536 _SCHEDULE_TIMEOUT, "waiting for $method $path"); |
1538 }); | 1537 }); |
1539 _handlers.add(handlerCompleter.future); | 1538 _handlers.add(handlerCompleter.future); |
1540 } | 1539 } |
1541 | 1540 |
1542 /// Ignore all requests with the given [method] and [path]. If one is | 1541 /// Ignore all requests with the given [method] and [path]. If one is |
1543 /// received, don't respond to it. | 1542 /// received, don't respond to it. |
1544 void ignore(String method, String path) => | 1543 void ignore(String method, String path) => |
1545 _ignored.add(new Pair(method, path)); | 1544 _ignored.add(new Pair(method, path)); |
1546 | 1545 |
1547 /// Raises an error complaining of an unexpected request. | 1546 /// Raises an error complaining of an unexpected request. |
1548 void _awaitHandle(HttpRequest request, HttpResponse response) { | 1547 void _awaitHandle(HttpRequest request) { |
1549 if (_ignored.contains(new Pair(request.method, request.path))) return; | 1548 HttpResponse response = request.response; |
| 1549 if (_ignored.contains(new Pair(request.method, request.uri.path))) return; |
1550 var future = timeout(defer(() { | 1550 var future = timeout(defer(() { |
1551 if (_handlers.isEmpty) { | 1551 if (_handlers.isEmpty) { |
1552 fail('Unexpected ${request.method} request to ${request.path}.'); | 1552 fail('Unexpected ${request.method} request to ${request.uri.path}.'); |
1553 } | 1553 } |
1554 return _handlers.removeFirst(); | 1554 return _handlers.removeFirst(); |
1555 }).then((handler) { | 1555 }).then((handler) { |
1556 handler(request, response); | 1556 handler(request, response); |
1557 }), _SCHEDULE_TIMEOUT, "waiting for a handler for ${request.method} " | 1557 }), _SCHEDULE_TIMEOUT, "waiting for a handler for ${request.method} " |
1558 "${request.path}"); | 1558 "${request.uri.path}"); |
1559 expect(future, completes); | 1559 expect(future, completes); |
1560 } | 1560 } |
1561 } | 1561 } |
1562 | 1562 |
1563 /// Takes a simple data structure (composed of [Map]s, [List]s, scalar objects, | 1563 /// Takes a simple data structure (composed of [Map]s, [List]s, scalar objects, |
1564 /// and [Future]s) and recursively resolves all the [Future]s contained within. | 1564 /// and [Future]s) and recursively resolves all the [Future]s contained within. |
1565 /// Completes with the fully resolved structure. | 1565 /// Completes with the fully resolved structure. |
1566 Future _awaitObject(object) { | 1566 Future _awaitObject(object) { |
1567 // Unroll nested futures. | 1567 // Unroll nested futures. |
1568 if (object is Future) return object.then(_awaitObject); | 1568 if (object is Future) return object.then(_awaitObject); |
(...skipping 11 matching lines...) Expand all Loading... |
1580 var map = {}; | 1580 var map = {}; |
1581 for (var pair in resolvedPairs) { | 1581 for (var pair in resolvedPairs) { |
1582 map[pair.first] = pair.last; | 1582 map[pair.first] = pair.last; |
1583 } | 1583 } |
1584 return map; | 1584 return map; |
1585 }); | 1585 }); |
1586 } | 1586 } |
1587 | 1587 |
1588 /// Schedules a callback to be called as part of the test case. | 1588 /// Schedules a callback to be called as part of the test case. |
1589 void _schedule(_ScheduledEvent event) { | 1589 void _schedule(_ScheduledEvent event) { |
1590 if (_scheduled == null) _scheduled = []; | 1590 if (_scheduled == null) _scheduled = new Queue(); |
1591 _scheduled.add(event); | 1591 _scheduled.addLast(event); |
1592 } | 1592 } |
1593 | 1593 |
1594 /// Like [_schedule], but pipes the return value of [event] to a returned | 1594 /// Like [_schedule], but pipes the return value of [event] to a returned |
1595 /// [Future]. | 1595 /// [Future]. |
1596 Future _scheduleValue(_ScheduledEvent event) { | 1596 Future _scheduleValue(_ScheduledEvent event) { |
1597 var completer = new Completer(); | 1597 var completer = new Completer(); |
1598 _schedule((parentDir) { | 1598 _schedule((parentDir) { |
1599 chainToCompleter(event(parentDir), completer); | 1599 chainToCompleter(event(parentDir), completer); |
1600 return completer.future; | 1600 return completer.future; |
1601 }); | 1601 }); |
1602 return completer.future; | 1602 return completer.future; |
1603 } | 1603 } |
1604 | 1604 |
1605 /// Schedules a callback to be called after the test case has completed, even | 1605 /// Schedules a callback to be called after the test case has completed, even |
1606 /// if it failed. | 1606 /// if it failed. |
1607 void _scheduleCleanup(_ScheduledEvent event) { | 1607 void _scheduleCleanup(_ScheduledEvent event) { |
1608 if (_scheduledCleanup == null) _scheduledCleanup = []; | 1608 if (_scheduledCleanup == null) _scheduledCleanup = new Queue(); |
1609 _scheduledCleanup.add(event); | 1609 _scheduledCleanup.addLast(event); |
1610 } | 1610 } |
1611 | 1611 |
1612 /// Schedules a callback to be called after the test case has completed, but | 1612 /// Schedules a callback to be called after the test case has completed, but |
1613 /// only if it failed. | 1613 /// only if it failed. |
1614 void _scheduleOnException(_ScheduledEvent event) { | 1614 void _scheduleOnException(_ScheduledEvent event) { |
1615 if (_scheduledOnException == null) _scheduledOnException = []; | 1615 if (_scheduledOnException == null) _scheduledOnException = new Queue(); |
1616 _scheduledOnException.add(event); | 1616 _scheduledOnException.addLast(event); |
1617 } | 1617 } |
1618 | 1618 |
1619 /// Like [expect], but for [Future]s that complete as part of the scheduled | 1619 /// Like [expect], but for [Future]s that complete as part of the scheduled |
1620 /// test. This is necessary to ensure that the exception thrown by the | 1620 /// test. This is necessary to ensure that the exception thrown by the |
1621 /// expectation failing is handled by the scheduler. | 1621 /// expectation failing is handled by the scheduler. |
1622 /// | 1622 /// |
1623 /// Note that [matcher] matches against the completed value of [actual], so | 1623 /// Note that [matcher] matches against the completed value of [actual], so |
1624 /// calling [completion] is unnecessary. | 1624 /// calling [completion] is unnecessary. |
1625 void expectLater(Future actual, matcher, {String reason, | 1625 void expectLater(Future actual, matcher, {String reason, |
1626 FailureHandler failureHandler, bool verbose: false}) { | 1626 FailureHandler failureHandler, bool verbose: false}) { |
1627 _schedule((_) { | 1627 _schedule((_) { |
1628 return actual.then((value) { | 1628 return actual.then((value) { |
1629 expect(value, matcher, reason: reason, failureHandler: failureHandler, | 1629 expect(value, matcher, reason: reason, failureHandler: failureHandler, |
1630 verbose: false); | 1630 verbose: false); |
1631 }); | 1631 }); |
1632 }); | 1632 }); |
1633 } | 1633 } |
OLD | NEW |