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 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
103 var stream; | 103 var stream; |
104 try { | 104 try { |
105 stream = baseDir.load(path); | 105 stream = baseDir.load(path); |
106 } catch (e) { | 106 } catch (e) { |
107 response.statusCode = 404; | 107 response.statusCode = 404; |
108 response.contentLength = 0; | 108 response.contentLength = 0; |
109 response.outputStream.close(); | 109 response.outputStream.close(); |
110 return; | 110 return; |
111 } | 111 } |
112 | 112 |
113 stream.toBytes().then((data) { | 113 var future = consumeInputStream(stream); |
| 114 future.then((data) { |
114 response.statusCode = 200; | 115 response.statusCode = 200; |
115 response.contentLength = data.length; | 116 response.contentLength = data.length; |
116 response.outputStream.write(data); | 117 response.outputStream.write(data); |
117 response.outputStream.close(); | 118 response.outputStream.close(); |
118 }).catchError((e) { | 119 }).catchError((e) { |
119 print("Exception while handling ${request.uri}: $e"); | 120 print("Exception while handling ${request.uri}: $e"); |
120 response.statusCode = 500; | 121 response.statusCode = 500; |
121 response.reasonPhrase = e.message; | 122 response.reasonPhrase = e.message; |
122 response.outputStream.close(); | 123 response.outputStream.close(); |
123 }); | 124 }); |
(...skipping 646 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
770 /// system entry within [dir]. Returns a [Future] that completes to `null` if | 771 /// system entry within [dir]. Returns a [Future] that completes to `null` if |
771 /// the entry is valid, or throws an error if it failed. | 772 /// the entry is valid, or throws an error if it failed. |
772 Future validate(String dir); | 773 Future validate(String dir); |
773 | 774 |
774 /// Deletes the file or directory within [dir]. Returns a [Future] that is | 775 /// Deletes the file or directory within [dir]. Returns a [Future] that is |
775 /// completed after the deletion is done. | 776 /// completed after the deletion is done. |
776 Future delete(String dir); | 777 Future delete(String dir); |
777 | 778 |
778 /// Loads the file at [path] from within this descriptor. If [path] is empty, | 779 /// Loads the file at [path] from within this descriptor. If [path] is empty, |
779 /// loads the contents of the descriptor itself. | 780 /// loads the contents of the descriptor itself. |
780 ByteStream load(List<String> path); | 781 InputStream load(List<String> path); |
781 | 782 |
782 /// Schedules the directory to be created before Pub is run with | 783 /// Schedules the directory to be created before Pub is run with |
783 /// [schedulePub]. The directory will be created relative to the sandbox | 784 /// [schedulePub]. The directory will be created relative to the sandbox |
784 /// directory. | 785 /// directory. |
785 // TODO(nweiz): Use implicit closurization once issue 2984 is fixed. | 786 // TODO(nweiz): Use implicit closurization once issue 2984 is fixed. |
786 void scheduleCreate() => _schedule((dir) => this.create(dir)); | 787 void scheduleCreate() => _schedule((dir) => this.create(dir)); |
787 | 788 |
788 /// Schedules the file or directory to be deleted recursively. | 789 /// Schedules the file or directory to be deleted recursively. |
789 void scheduleDelete() => _schedule((dir) => this.delete(dir)); | 790 void scheduleDelete() => _schedule((dir) => this.delete(dir)); |
790 | 791 |
(...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
890 if (text == contents) return null; | 891 if (text == contents) return null; |
891 | 892 |
892 throw new ExpectException( | 893 throw new ExpectException( |
893 'File $file should contain:\n\n$contents\n\n' | 894 'File $file should contain:\n\n$contents\n\n' |
894 'but contained:\n\n$text'); | 895 'but contained:\n\n$text'); |
895 }); | 896 }); |
896 }); | 897 }); |
897 } | 898 } |
898 | 899 |
899 /// Loads the contents of the file. | 900 /// Loads the contents of the file. |
900 ByteStream load(List<String> path) { | 901 InputStream load(List<String> path) { |
901 if (!path.isEmpty) { | 902 if (!path.isEmpty) { |
902 var joinedPath = Strings.join(path, '/'); | 903 var joinedPath = Strings.join(path, '/'); |
903 throw "Can't load $joinedPath from within $name: not a directory."; | 904 throw "Can't load $joinedPath from within $name: not a directory."; |
904 } | 905 } |
905 | 906 |
906 return new ByteStream.fromBytes(contents.charCodes); | 907 var stream = new ListInputStream(); |
| 908 stream.write(contents.charCodes); |
| 909 stream.markEndOfStream(); |
| 910 return stream; |
907 } | 911 } |
908 } | 912 } |
909 | 913 |
910 /// Describes a directory and its contents. These are used both for setting up | 914 /// Describes a directory and its contents. These are used both for setting up |
911 /// an expected directory tree before running a test, and for validating that | 915 /// an expected directory tree before running a test, and for validating that |
912 /// the file system matches some expectations after running it. | 916 /// the file system matches some expectations after running it. |
913 class DirectoryDescriptor extends Descriptor { | 917 class DirectoryDescriptor extends Descriptor { |
914 /// The files and directories contained in this directory. | 918 /// The files and directories contained in this directory. |
915 final List<Descriptor> contents; | 919 final List<Descriptor> contents; |
916 | 920 |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
948 // Validate each of the items in this directory. | 952 // Validate each of the items in this directory. |
949 final entryFutures = | 953 final entryFutures = |
950 contents.mappedBy((entry) => entry.validate(dir)).toList(); | 954 contents.mappedBy((entry) => entry.validate(dir)).toList(); |
951 | 955 |
952 // If they are all valid, the directory is valid. | 956 // If they are all valid, the directory is valid. |
953 return Future.wait(entryFutures).then((entries) => null); | 957 return Future.wait(entryFutures).then((entries) => null); |
954 }); | 958 }); |
955 } | 959 } |
956 | 960 |
957 /// Loads [path] from within this directory. | 961 /// Loads [path] from within this directory. |
958 ByteStream load(List<String> path) { | 962 InputStream load(List<String> path) { |
959 if (path.isEmpty) { | 963 if (path.isEmpty) { |
960 throw "Can't load the contents of $name: is a directory."; | 964 throw "Can't load the contents of $name: is a directory."; |
961 } | 965 } |
962 | 966 |
963 for (var descriptor in contents) { | 967 for (var descriptor in contents) { |
964 if (descriptor.name == path[0]) { | 968 if (descriptor.name == path[0]) { |
965 return descriptor.load(path.getRange(1, path.length - 1)); | 969 return descriptor.load(path.getRange(1, path.length - 1)); |
966 } | 970 } |
967 } | 971 } |
968 | 972 |
969 throw "Directory $name doesn't contain ${Strings.join(path, '/')}."; | 973 throw "Directory $name doesn't contain ${Strings.join(path, '/')}."; |
970 } | 974 } |
971 } | 975 } |
972 | 976 |
973 /// Wraps a [Future] that will complete to a [Descriptor] and makes it behave | 977 /// Wraps a [Future] that will complete to a [Descriptor] and makes it behave |
974 /// like a concrete [Descriptor]. This is necessary when the contents of the | 978 /// like a concrete [Descriptor]. This is necessary when the contents of the |
975 /// descriptor depends on information that's not available until part of the | 979 /// descriptor depends on information that's not available until part of the |
976 /// test run is completed. | 980 /// test run is completed. |
977 class FutureDescriptor extends Descriptor { | 981 class FutureDescriptor extends Descriptor { |
978 Future<Descriptor> _future; | 982 Future<Descriptor> _future; |
979 | 983 |
980 FutureDescriptor(this._future) : super('<unknown>'); | 984 FutureDescriptor(this._future) : super('<unknown>'); |
981 | 985 |
982 Future create(dir) => _future.then((desc) => desc.create(dir)); | 986 Future create(dir) => _future.then((desc) => desc.create(dir)); |
983 | 987 |
984 Future validate(dir) => _future.then((desc) => desc.validate(dir)); | 988 Future validate(dir) => _future.then((desc) => desc.validate(dir)); |
985 | 989 |
986 Future delete(dir) => _future.then((desc) => desc.delete(dir)); | 990 Future delete(dir) => _future.then((desc) => desc.delete(dir)); |
987 | 991 |
988 ByteStream load(List<String> path) { | 992 InputStream load(List<String> path) { |
989 var controller = new StreamController<List<int>>(); | 993 var resultStream = new ListInputStream(); |
990 _future.then((desc) => store(desc.load(path), controller)); | 994 _future.then((desc) => pipeInputToInput(desc.load(path), resultStream)); |
991 return new ByteStream(controller.stream); | 995 return resultStream; |
992 } | 996 } |
993 } | 997 } |
994 | 998 |
995 /// Describes a Git repository and its contents. | 999 /// Describes a Git repository and its contents. |
996 class GitRepoDescriptor extends DirectoryDescriptor { | 1000 class GitRepoDescriptor extends DirectoryDescriptor { |
997 GitRepoDescriptor(Pattern name, List<Descriptor> contents) | 1001 GitRepoDescriptor(Pattern name, List<Descriptor> contents) |
998 : super(name, contents); | 1002 : super(name, contents); |
999 | 1003 |
1000 /// Creates the Git repository and commits the contents. | 1004 /// Creates the Git repository and commits the contents. |
1001 Future<Directory> create(parentDir) { | 1005 Future<Directory> create(parentDir) { |
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1074 | 1078 |
1075 /// Creates the files and directories within this tar file, then archives | 1079 /// Creates the files and directories within this tar file, then archives |
1076 /// them, compresses them, and saves the result to [parentDir]. | 1080 /// them, compresses them, and saves the result to [parentDir]. |
1077 Future<File> create(parentDir) { | 1081 Future<File> create(parentDir) { |
1078 // TODO(rnystrom): Use withTempDir(). | 1082 // TODO(rnystrom): Use withTempDir(). |
1079 var tempDir; | 1083 var tempDir; |
1080 return createTempDir().then((_tempDir) { | 1084 return createTempDir().then((_tempDir) { |
1081 tempDir = _tempDir; | 1085 tempDir = _tempDir; |
1082 return Future.wait(contents.mappedBy((child) => child.create(tempDir))); | 1086 return Future.wait(contents.mappedBy((child) => child.create(tempDir))); |
1083 }).then((createdContents) { | 1087 }).then((createdContents) { |
1084 return createTarGz(createdContents, baseDir: tempDir).toBytes(); | 1088 return consumeInputStream(createTarGz(createdContents, baseDir: tempDir)); |
1085 }).then((bytes) { | 1089 }).then((bytes) { |
1086 return new File(join(parentDir, _stringName)).writeAsBytes(bytes); | 1090 return new File(join(parentDir, _stringName)).writeAsBytes(bytes); |
1087 }).then((file) { | 1091 }).then((file) { |
1088 return deleteDir(tempDir).then((_) => file); | 1092 return deleteDir(tempDir).then((_) => file); |
1089 }); | 1093 }); |
1090 } | 1094 } |
1091 | 1095 |
1092 /// Validates that the `.tar.gz` file at [path] contains the expected | 1096 /// Validates that the `.tar.gz` file at [path] contains the expected |
1093 /// contents. | 1097 /// contents. |
1094 Future validate(String path) { | 1098 Future validate(String path) { |
1095 throw "TODO(nweiz): implement this"; | 1099 throw "TODO(nweiz): implement this"; |
1096 } | 1100 } |
1097 | 1101 |
1098 Future delete(dir) { | 1102 Future delete(dir) { |
1099 throw new UnsupportedError(''); | 1103 throw new UnsupportedError(''); |
1100 } | 1104 } |
1101 | 1105 |
1102 /// Loads the contents of this tar file. | 1106 /// Loads the contents of this tar file. |
1103 ByteStream load(List<String> path) { | 1107 InputStream load(List<String> path) { |
1104 if (!path.isEmpty) { | 1108 if (!path.isEmpty) { |
1105 var joinedPath = Strings.join(path, '/'); | 1109 var joinedPath = Strings.join(path, '/'); |
1106 throw "Can't load $joinedPath from within $name: not a directory."; | 1110 throw "Can't load $joinedPath from within $name: not a directory."; |
1107 } | 1111 } |
1108 | 1112 |
1109 var controller = new StreamController<List<int>>(); | 1113 var sinkStream = new ListInputStream(); |
1110 var tempDir; | 1114 var tempDir; |
1111 // TODO(rnystrom): Use withTempDir() here. | 1115 // TODO(rnystrom): Use withTempDir() here. |
1112 // TODO(nweiz): propagate any errors to the return value. See issue 3657. | 1116 // TODO(nweiz): propagate any errors to the return value. See issue 3657. |
1113 createTempDir().then((_tempDir) { | 1117 createTempDir().then((_tempDir) { |
1114 tempDir = _tempDir; | 1118 tempDir = _tempDir; |
1115 return create(tempDir); | 1119 return create(tempDir); |
1116 }).then((tar) { | 1120 }).then((tar) { |
1117 var sourceStream = tar.openInputStream(); | 1121 var sourceStream = tar.openInputStream(); |
1118 return store(wrapInputStream(sourceStream), controller).then((_) { | 1122 return pipeInputToInput(sourceStream, sinkStream).then((_) { |
1119 tempDir.delete(recursive: true); | 1123 tempDir.delete(recursive: true); |
1120 }); | 1124 }); |
1121 }); | 1125 }); |
1122 return new ByteStream(controller.stream); | 1126 return sinkStream; |
1123 } | 1127 } |
1124 } | 1128 } |
1125 | 1129 |
1126 /// A descriptor that validates that no file exists with the given name. | 1130 /// A descriptor that validates that no file exists with the given name. |
1127 class NothingDescriptor extends Descriptor { | 1131 class NothingDescriptor extends Descriptor { |
1128 NothingDescriptor(String name) : super(name); | 1132 NothingDescriptor(String name) : super(name); |
1129 | 1133 |
1130 Future create(dir) => new Future.immediate(null); | 1134 Future create(dir) => new Future.immediate(null); |
1131 Future delete(dir) => new Future.immediate(null); | 1135 Future delete(dir) => new Future.immediate(null); |
1132 | 1136 |
1133 Future validate(String dir) { | 1137 Future validate(String dir) { |
1134 return exists(join(dir, name)).then((exists) { | 1138 return exists(join(dir, name)).then((exists) { |
1135 if (exists) { | 1139 if (exists) { |
1136 throw new ExpectException('File $name in $dir should not exist.'); | 1140 throw new ExpectException('File $name in $dir should not exist.'); |
1137 } | 1141 } |
1138 }); | 1142 }); |
1139 } | 1143 } |
1140 | 1144 |
1141 ByteStream load(List<String> path) { | 1145 InputStream load(List<String> path) { |
1142 if (path.isEmpty) { | 1146 if (path.isEmpty) { |
1143 throw "Can't load the contents of $name: it doesn't exist."; | 1147 throw "Can't load the contents of $name: it doesn't exist."; |
1144 } else { | 1148 } else { |
1145 throw "Can't load ${Strings.join(path, '/')} from within $name: $name " | 1149 throw "Can't load ${Strings.join(path, '/')} from within $name: $name " |
1146 "doesn't exist."; | 1150 "doesn't exist."; |
1147 } | 1151 } |
1148 } | 1152 } |
1149 } | 1153 } |
1150 | 1154 |
1151 /// A function that creates a [Validator] subclass. | 1155 /// A function that creates a [Validator] subclass. |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1201 /// Before running the test, either [shouldExit] or [kill] must be called on | 1205 /// Before running the test, either [shouldExit] or [kill] must be called on |
1202 /// this to ensure that the process terminates when expected. | 1206 /// this to ensure that the process terminates when expected. |
1203 /// | 1207 /// |
1204 /// If the test fails, this will automatically print out any remaining stdout | 1208 /// If the test fails, this will automatically print out any remaining stdout |
1205 /// and stderr from the process to aid debugging. | 1209 /// and stderr from the process to aid debugging. |
1206 class ScheduledProcess { | 1210 class ScheduledProcess { |
1207 /// The name of the process. Used for error reporting. | 1211 /// The name of the process. Used for error reporting. |
1208 final String name; | 1212 final String name; |
1209 | 1213 |
1210 /// The process future that's scheduled to run. | 1214 /// The process future that's scheduled to run. |
1211 Future<PubProcess> _processFuture; | 1215 Future<Process> _processFuture; |
1212 | 1216 |
1213 /// The process that's scheduled to run. It may be null. | 1217 /// The process that's scheduled to run. It may be null. |
1214 Process _process; | 1218 Process _process; |
1215 | 1219 |
1216 /// The exit code of the scheduled program. It may be null. | 1220 /// The exit code of the scheduled program. It may be null. |
1217 int _exitCode; | 1221 int _exitCode; |
1218 | 1222 |
1219 /// A future that will complete to a list of all the lines emitted on the | 1223 /// A [StringInputStream] wrapping the stdout of the process that's scheduled |
1220 /// process's standard output stream. This is independent of what data is read | 1224 /// to run. |
1221 /// from [_stdout]. | 1225 final Future<StringInputStream> _stdoutFuture; |
1222 Future<List<String>> _stdoutLines; | |
1223 | 1226 |
1224 /// A [Stream] of stdout lines emitted by the process that's scheduled to run. | 1227 /// A [StringInputStream] wrapping the stderr of the process that's scheduled |
1225 /// It may be null. | 1228 /// to run. |
1226 Stream<String> _stdout; | 1229 final Future<StringInputStream> _stderrFuture; |
1227 | |
1228 /// A [Future] that will resolve to [_stdout] once it's available. | |
1229 Future get _stdoutFuture => _processFuture.then((_) => _stdout); | |
1230 | |
1231 /// A [StreamSubscription] that controls [_stdout]. | |
1232 StreamSubscription _stdoutSubscription; | |
1233 | |
1234 /// A future that will complete to a list of all the lines emitted on the | |
1235 /// process's standard error stream. This is independent of what data is read | |
1236 /// from [_stderr]. | |
1237 Future<List<String>> _stderrLines; | |
1238 | |
1239 /// A [Stream] of stderr lines emitted by the process that's scheduled to run. | |
1240 /// It may be null. | |
1241 Stream<String> _stderr; | |
1242 | |
1243 /// A [Future] that will resolve to [_stderr] once it's available. | |
1244 Future get _stderrFuture => _processFuture.then((_) => _stderr); | |
1245 | |
1246 /// A [StreamSubscription] that controls [_stderr]. | |
1247 StreamSubscription _stderrSubscription; | |
1248 | 1230 |
1249 /// The exit code of the process that's scheduled to run. This will naturally | 1231 /// The exit code of the process that's scheduled to run. This will naturally |
1250 /// only complete once the process has terminated. | 1232 /// only complete once the process has terminated. |
1251 Future<int> get _exitCodeFuture => _exitCodeCompleter.future; | 1233 Future<int> get _exitCodeFuture => _exitCodeCompleter.future; |
1252 | 1234 |
1253 /// The completer for [_exitCode]. | 1235 /// The completer for [_exitCode]. |
1254 final Completer<int> _exitCodeCompleter = new Completer(); | 1236 final Completer<int> _exitCodeCompleter = new Completer(); |
1255 | 1237 |
1256 /// Whether the user has scheduled the end of this process by calling either | 1238 /// Whether the user has scheduled the end of this process by calling either |
1257 /// [shouldExit] or [kill]. | 1239 /// [shouldExit] or [kill]. |
1258 bool _endScheduled = false; | 1240 bool _endScheduled = false; |
1259 | 1241 |
1260 /// Whether the process is expected to terminate at this point. | 1242 /// Whether the process is expected to terminate at this point. |
1261 bool _endExpected = false; | 1243 bool _endExpected = false; |
1262 | 1244 |
1263 /// Wraps a [Process] [Future] in a scheduled process. | 1245 /// Wraps a [Process] [Future] in a scheduled process. |
1264 ScheduledProcess(this.name, Future<PubProcess> process) | 1246 ScheduledProcess(this.name, Future<Process> process) |
1265 : _processFuture = process { | 1247 : _processFuture = process, |
1266 var pairFuture = process.then((p) { | 1248 _stdoutFuture = process.then((p) => new StringInputStream(p.stdout)), |
| 1249 _stderrFuture = process.then((p) => new StringInputStream(p.stderr)) { |
| 1250 process.then((p) { |
1267 _process = p; | 1251 _process = p; |
1268 | |
1269 var stdoutTee = tee(p.stdout.handleError((e) { | |
1270 registerException(e.error, e.stackTrace); | |
1271 })); | |
1272 var stdoutPair = streamWithSubscription(stdoutTee.last); | |
1273 _stdout = stdoutPair.first; | |
1274 _stdoutSubscription = stdoutPair.last; | |
1275 | |
1276 var stderrTee = tee(p.stderr.handleError((e) { | |
1277 registerException(e.error, e.stackTrace); | |
1278 })); | |
1279 var stderrPair = streamWithSubscription(stderrTee.last); | |
1280 _stderr = stderrPair.first; | |
1281 _stderrSubscription = stderrPair.last; | |
1282 | |
1283 return new Pair(stdoutTee.first, stderrTee.first); | |
1284 }); | 1252 }); |
1285 | 1253 |
1286 _stdoutLines = pairFuture.then((pair) => pair.first.toList()); | |
1287 _stderrLines = pairFuture.then((pair) => pair.last.toList()); | |
1288 | |
1289 _schedule((_) { | 1254 _schedule((_) { |
1290 if (!_endScheduled) { | 1255 if (!_endScheduled) { |
1291 throw new StateError("Scheduled process $name must have shouldExit() " | 1256 throw new StateError("Scheduled process $name must have shouldExit() " |
1292 "or kill() called before the test is run."); | 1257 "or kill() called before the test is run."); |
1293 } | 1258 } |
1294 | 1259 |
1295 return process.then((p) => p.exitCode).then((exitCode) { | 1260 return process.then((p) { |
1296 if (_endExpected) { | 1261 p.onExit = (c) { |
1297 _exitCode = exitCode; | |
1298 _exitCodeCompleter.complete(exitCode); | |
1299 return; | |
1300 } | |
1301 | |
1302 // Sleep for half a second in case _endExpected is set in the next | |
1303 // scheduled event. | |
1304 sleep(500).then((_) { | |
1305 if (_endExpected) { | 1262 if (_endExpected) { |
1306 _exitCodeCompleter.complete(exitCode); | 1263 _exitCode = c; |
| 1264 _exitCodeCompleter.complete(c); |
1307 return; | 1265 return; |
1308 } | 1266 } |
1309 | 1267 |
1310 return _printStreams().then((_) { | 1268 // Sleep for half a second in case _endExpected is set in the next |
1311 registerException(new ExpectException("Process $name ended " | 1269 // scheduled event. |
1312 "earlier than scheduled with exit code $exitCode")); | 1270 sleep(500).then((_) { |
| 1271 if (_endExpected) { |
| 1272 _exitCodeCompleter.complete(c); |
| 1273 return; |
| 1274 } |
| 1275 |
| 1276 _printStreams().then((_) { |
| 1277 registerException(new ExpectException("Process $name ended " |
| 1278 "earlier than scheduled with exit code $c")); |
| 1279 }); |
1313 }); | 1280 }); |
1314 }); | 1281 }; |
1315 }); | 1282 }); |
1316 }); | 1283 }); |
1317 | 1284 |
1318 _scheduleOnException((_) { | 1285 _scheduleOnException((_) { |
1319 if (_process == null) return; | 1286 if (_process == null) return; |
1320 | 1287 |
1321 if (_exitCode == null) { | 1288 if (_exitCode == null) { |
1322 print("\nKilling process $name prematurely."); | 1289 print("\nKilling process $name prematurely."); |
1323 _endExpected = true; | 1290 _endExpected = true; |
1324 _process.kill(); | 1291 _process.kill(); |
1325 } | 1292 } |
1326 | 1293 |
1327 return _printStreams(); | 1294 return _printStreams(); |
1328 }); | 1295 }); |
1329 | 1296 |
1330 _scheduleCleanup((_) { | 1297 _scheduleCleanup((_) { |
1331 if (_process == null) return; | 1298 if (_process == null) return; |
1332 // Ensure that the process is dead and we aren't waiting on any IO. | 1299 // Ensure that the process is dead and we aren't waiting on any IO. |
1333 _process.kill(); | 1300 _process.kill(); |
1334 _stdoutSubscription.cancel(); | 1301 _process.stdout.close(); |
1335 _stderrSubscription.cancel(); | 1302 _process.stderr.close(); |
1336 }); | 1303 }); |
1337 } | 1304 } |
1338 | 1305 |
1339 /// Reads the next line of stdout from the process. | 1306 /// Reads the next line of stdout from the process. |
1340 Future<String> nextLine() { | 1307 Future<String> nextLine() { |
1341 return _scheduleValue((_) { | 1308 return _scheduleValue((_) { |
1342 return timeout(_stdoutFuture.then((stream) => streamFirst(stream)), | 1309 return timeout(_stdoutFuture.then((stream) => readLine(stream)), |
1343 _SCHEDULE_TIMEOUT, | 1310 _SCHEDULE_TIMEOUT, |
1344 "waiting for the next stdout line from process $name"); | 1311 "waiting for the next stdout line from process $name"); |
1345 }); | 1312 }); |
1346 } | 1313 } |
1347 | 1314 |
1348 /// Reads the next line of stderr from the process. | 1315 /// Reads the next line of stderr from the process. |
1349 Future<String> nextErrLine() { | 1316 Future<String> nextErrLine() { |
1350 return _scheduleValue((_) { | 1317 return _scheduleValue((_) { |
1351 return timeout(_stderrFuture.then((stream) => streamFirst(stream)), | 1318 return timeout(_stderrFuture.then((stream) => readLine(stream)), |
1352 _SCHEDULE_TIMEOUT, | 1319 _SCHEDULE_TIMEOUT, |
1353 "waiting for the next stderr line from process $name"); | 1320 "waiting for the next stderr line from process $name"); |
1354 }); | 1321 }); |
1355 } | 1322 } |
1356 | 1323 |
1357 /// Reads the remaining stdout from the process. This should only be called | 1324 /// Reads the remaining stdout from the process. This should only be called |
1358 /// after kill() or shouldExit(). | 1325 /// after kill() or shouldExit(). |
1359 Future<String> remainingStdout() { | 1326 Future<String> remainingStdout() { |
1360 if (!_endScheduled) { | 1327 if (!_endScheduled) { |
1361 throw new StateError("remainingStdout() should only be called after " | 1328 throw new StateError("remainingStdout() should only be called after " |
1362 "kill() or shouldExit()."); | 1329 "kill() or shouldExit()."); |
1363 } | 1330 } |
1364 | 1331 |
1365 return _scheduleValue((_) { | 1332 return _scheduleValue((_) { |
1366 return timeout(_stdoutFuture.then((stream) => stream.toList()) | 1333 return timeout(_stdoutFuture.then(consumeStringInputStream), |
1367 .then((lines) => lines.join("\n")), | |
1368 _SCHEDULE_TIMEOUT, | 1334 _SCHEDULE_TIMEOUT, |
1369 "waiting for the last stdout line from process $name"); | 1335 "waiting for the last stdout line from process $name"); |
1370 }); | 1336 }); |
1371 } | 1337 } |
1372 | 1338 |
1373 /// Reads the remaining stderr from the process. This should only be called | 1339 /// Reads the remaining stderr from the process. This should only be called |
1374 /// after kill() or shouldExit(). | 1340 /// after kill() or shouldExit(). |
1375 Future<String> remainingStderr() { | 1341 Future<String> remainingStderr() { |
1376 if (!_endScheduled) { | 1342 if (!_endScheduled) { |
1377 throw new StateError("remainingStderr() should only be called after " | 1343 throw new StateError("remainingStderr() should only be called after " |
1378 "kill() or shouldExit()."); | 1344 "kill() or shouldExit()."); |
1379 } | 1345 } |
1380 | 1346 |
1381 return _scheduleValue((_) { | 1347 return _scheduleValue((_) { |
1382 return timeout(_stderrFuture.then((stream) => stream.toList()) | 1348 return timeout(_stderrFuture.then(consumeStringInputStream), |
1383 .then((lines) => lines.join("\n")), | |
1384 _SCHEDULE_TIMEOUT, | 1349 _SCHEDULE_TIMEOUT, |
1385 "waiting for the last stderr line from process $name"); | 1350 "waiting for the last stderr line from process $name"); |
1386 }); | 1351 }); |
1387 } | 1352 } |
1388 | 1353 |
1389 /// Writes [line] to the process as stdin. | 1354 /// Writes [line] to the process as stdin. |
1390 void writeLine(String line) { | 1355 void writeLine(String line) { |
1391 _schedule((_) => _processFuture.then( | 1356 _schedule((_) => _processFuture.then( |
1392 (p) => p.stdin.write('$line\n'.charCodes))); | 1357 (p) => p.stdin.writeString('$line\n'))); |
1393 } | 1358 } |
1394 | 1359 |
1395 /// Kills the process, and waits until it's dead. | 1360 /// Kills the process, and waits until it's dead. |
1396 void kill() { | 1361 void kill() { |
1397 _endScheduled = true; | 1362 _endScheduled = true; |
1398 _schedule((_) { | 1363 _schedule((_) { |
1399 _endExpected = true; | 1364 _endExpected = true; |
1400 _process.kill(); | 1365 _process.kill(); |
1401 timeout(_exitCodeFuture, _SCHEDULE_TIMEOUT, | 1366 timeout(_exitCodeCompleter.future, _SCHEDULE_TIMEOUT, |
1402 "waiting for process $name to die"); | 1367 "waiting for process $name to die"); |
1403 }); | 1368 }); |
1404 } | 1369 } |
1405 | 1370 |
1406 /// Waits for the process to exit, and verifies that the exit code matches | 1371 /// Waits for the process to exit, and verifies that the exit code matches |
1407 /// [expectedExitCode] (if given). | 1372 /// [expectedExitCode] (if given). |
1408 void shouldExit([int expectedExitCode]) { | 1373 void shouldExit([int expectedExitCode]) { |
1409 _endScheduled = true; | 1374 _endScheduled = true; |
1410 _schedule((_) { | 1375 _schedule((_) { |
1411 _endExpected = true; | 1376 _endExpected = true; |
1412 return timeout(_exitCodeFuture, _SCHEDULE_TIMEOUT, | 1377 return timeout(_exitCodeCompleter.future, _SCHEDULE_TIMEOUT, |
1413 "waiting for process $name to exit").then((exitCode) { | 1378 "waiting for process $name to exit").then((exitCode) { |
1414 if (expectedExitCode != null) { | 1379 if (expectedExitCode != null) { |
1415 expect(exitCode, equals(expectedExitCode)); | 1380 expect(exitCode, equals(expectedExitCode)); |
1416 } | 1381 } |
1417 }); | 1382 }); |
1418 }); | 1383 }); |
1419 } | 1384 } |
1420 | 1385 |
1421 /// Prints the remaining data in the process's stdout and stderr streams. | 1386 /// Prints the remaining data in the process's stdout and stderr streams. |
1422 /// Prints nothing if the streams are empty. | 1387 /// Prints nothing if the straems are empty. |
1423 Future _printStreams() { | 1388 Future _printStreams() { |
1424 void printStream(String streamName, List<String> lines) { | 1389 Future printStream(String streamName, StringInputStream stream) { |
1425 if (lines.isEmpty) return; | 1390 return consumeStringInputStream(stream).then((output) { |
| 1391 if (output.isEmpty) return; |
1426 | 1392 |
1427 print('\nProcess $name $streamName:'); | 1393 print('\nProcess $name $streamName:'); |
1428 for (var line in lines) { | 1394 for (var line in output.trim().split("\n")) { |
1429 print('| $line'); | 1395 print('| $line'); |
1430 } | 1396 } |
| 1397 return; |
| 1398 }); |
1431 } | 1399 } |
1432 | 1400 |
1433 return _stdoutLines.then((stdoutLines) { | 1401 return _stdoutFuture.then((stdout) { |
1434 printStream('stdout', stdoutLines); | 1402 return _stderrFuture.then((stderr) { |
1435 return _stderrLines.then((stderrLines) { | 1403 return printStream('stdout', stdout) |
1436 printStream('stderr', stderrLines); | 1404 .then((_) => printStream('stderr', stderr)); |
1437 }); | 1405 }); |
1438 }); | 1406 }); |
1439 } | 1407 } |
1440 } | 1408 } |
1441 | 1409 |
1442 /// A class representing an [HttpServer] that's scheduled to run in the course | 1410 /// A class representing an [HttpServer] that's scheduled to run in the course |
1443 /// of the test. This class allows the server's request handling to be | 1411 /// of the test. This class allows the server's request handling to be |
1444 /// scheduled synchronously. All operations on this class are scheduled. | 1412 /// scheduled synchronously. All operations on this class are scheduled. |
1445 class ScheduledServer { | 1413 class ScheduledServer { |
1446 /// The wrapped server. | 1414 /// The wrapped server. |
(...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1583 /// calling [completion] is unnecessary. | 1551 /// calling [completion] is unnecessary. |
1584 void expectLater(Future actual, matcher, {String reason, | 1552 void expectLater(Future actual, matcher, {String reason, |
1585 FailureHandler failureHandler, bool verbose: false}) { | 1553 FailureHandler failureHandler, bool verbose: false}) { |
1586 _schedule((_) { | 1554 _schedule((_) { |
1587 return actual.then((value) { | 1555 return actual.then((value) { |
1588 expect(value, matcher, reason: reason, failureHandler: failureHandler, | 1556 expect(value, matcher, reason: reason, failureHandler: failureHandler, |
1589 verbose: false); | 1557 verbose: false); |
1590 }); | 1558 }); |
1591 }); | 1559 }); |
1592 } | 1560 } |
OLD | NEW |