| 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 |