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 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
88 var stream; | 88 var stream; |
89 try { | 89 try { |
90 stream = baseDir.load(path); | 90 stream = baseDir.load(path); |
91 } catch (e) { | 91 } catch (e) { |
92 response.statusCode = 404; | 92 response.statusCode = 404; |
93 response.contentLength = 0; | 93 response.contentLength = 0; |
94 response.outputStream.close(); | 94 response.outputStream.close(); |
95 return; | 95 return; |
96 } | 96 } |
97 | 97 |
98 var future = consumeInputStream(stream); | 98 stream.toBytes().then((data) { |
99 future.then((data) { | |
100 response.statusCode = 200; | 99 response.statusCode = 200; |
101 response.contentLength = data.length; | 100 response.contentLength = data.length; |
102 response.outputStream.write(data); | 101 response.outputStream.write(data); |
103 response.outputStream.close(); | 102 response.outputStream.close(); |
104 }).catchError((e) { | 103 }).catchError((e) { |
105 print("Exception while handling ${request.uri}: $e"); | 104 print("Exception while handling ${request.uri}: $e"); |
106 response.statusCode = 500; | 105 response.statusCode = 500; |
107 response.reasonPhrase = e.message; | 106 response.reasonPhrase = e.message; |
108 response.outputStream.close(); | 107 response.outputStream.close(); |
109 }); | 108 }); |
(...skipping 648 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
758 /// system entry within [dir]. Returns a [Future] that completes to `null` if | 757 /// system entry within [dir]. Returns a [Future] that completes to `null` if |
759 /// the entry is valid, or throws an error if it failed. | 758 /// the entry is valid, or throws an error if it failed. |
760 Future validate(String dir); | 759 Future validate(String dir); |
761 | 760 |
762 /// Deletes the file or directory within [dir]. Returns a [Future] that is | 761 /// Deletes the file or directory within [dir]. Returns a [Future] that is |
763 /// completed after the deletion is done. | 762 /// completed after the deletion is done. |
764 Future delete(String dir); | 763 Future delete(String dir); |
765 | 764 |
766 /// Loads the file at [path] from within this descriptor. If [path] is empty, | 765 /// Loads the file at [path] from within this descriptor. If [path] is empty, |
767 /// loads the contents of the descriptor itself. | 766 /// loads the contents of the descriptor itself. |
768 InputStream load(List<String> path); | 767 ByteStream load(List<String> path); |
769 | 768 |
770 /// Schedules the directory to be created before Pub is run with [runPub]. | 769 /// Schedules the directory to be created before Pub is run with [runPub]. |
771 /// The directory will be created relative to the sandbox directory. | 770 /// The directory will be created relative to the sandbox directory. |
772 // TODO(nweiz): Use implicit closurization once issue 2984 is fixed. | 771 // TODO(nweiz): Use implicit closurization once issue 2984 is fixed. |
773 void scheduleCreate() => _schedule((dir) => this.create(dir)); | 772 void scheduleCreate() => _schedule((dir) => this.create(dir)); |
774 | 773 |
775 /// Schedules the file or directory to be deleted recursively. | 774 /// Schedules the file or directory to be deleted recursively. |
776 void scheduleDelete() => _schedule((dir) => this.delete(dir)); | 775 void scheduleDelete() => _schedule((dir) => this.delete(dir)); |
777 | 776 |
778 /// Schedules the directory to be validated after Pub is run with [runPub]. | 777 /// Schedules the directory to be validated after Pub is run with [runPub]. |
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
873 return readTextFile(file).then((text) { | 872 return readTextFile(file).then((text) { |
874 if (text == contents) return null; | 873 if (text == contents) return null; |
875 | 874 |
876 Expect.fail('File $file should contain:\n\n$contents\n\n' | 875 Expect.fail('File $file should contain:\n\n$contents\n\n' |
877 'but contained:\n\n$text'); | 876 'but contained:\n\n$text'); |
878 }); | 877 }); |
879 }); | 878 }); |
880 } | 879 } |
881 | 880 |
882 /// Loads the contents of the file. | 881 /// Loads the contents of the file. |
883 InputStream load(List<String> path) { | 882 ByteStream load(List<String> path) { |
884 if (!path.isEmpty) { | 883 if (!path.isEmpty) { |
885 var joinedPath = Strings.join(path, '/'); | 884 var joinedPath = Strings.join(path, '/'); |
886 throw "Can't load $joinedPath from within $name: not a directory."; | 885 throw "Can't load $joinedPath from within $name: not a directory."; |
887 } | 886 } |
888 | 887 |
889 var stream = new ListInputStream(); | 888 return new ByteStream.fromBytes(contents.charCodes); |
890 stream.write(contents.charCodes); | |
891 stream.markEndOfStream(); | |
892 return stream; | |
893 } | 889 } |
894 } | 890 } |
895 | 891 |
896 /// Describes a directory and its contents. These are used both for setting up | 892 /// Describes a directory and its contents. These are used both for setting up |
897 /// an expected directory tree before running a test, and for validating that | 893 /// an expected directory tree before running a test, and for validating that |
898 /// the file system matches some expectations after running it. | 894 /// the file system matches some expectations after running it. |
899 class DirectoryDescriptor extends Descriptor { | 895 class DirectoryDescriptor extends Descriptor { |
900 /// The files and directories contained in this directory. | 896 /// The files and directories contained in this directory. |
901 final List<Descriptor> contents; | 897 final List<Descriptor> contents; |
902 | 898 |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
934 // Validate each of the items in this directory. | 930 // Validate each of the items in this directory. |
935 final entryFutures = | 931 final entryFutures = |
936 contents.mappedBy((entry) => entry.validate(dir)).toList(); | 932 contents.mappedBy((entry) => entry.validate(dir)).toList(); |
937 | 933 |
938 // If they are all valid, the directory is valid. | 934 // If they are all valid, the directory is valid. |
939 return Future.wait(entryFutures).then((entries) => null); | 935 return Future.wait(entryFutures).then((entries) => null); |
940 }); | 936 }); |
941 } | 937 } |
942 | 938 |
943 /// Loads [path] from within this directory. | 939 /// Loads [path] from within this directory. |
944 InputStream load(List<String> path) { | 940 ByteStream load(List<String> path) { |
945 if (path.isEmpty) { | 941 if (path.isEmpty) { |
946 throw "Can't load the contents of $name: is a directory."; | 942 throw "Can't load the contents of $name: is a directory."; |
947 } | 943 } |
948 | 944 |
949 for (var descriptor in contents) { | 945 for (var descriptor in contents) { |
950 if (descriptor.name == path[0]) { | 946 if (descriptor.name == path[0]) { |
951 return descriptor.load(path.getRange(1, path.length - 1)); | 947 return descriptor.load(path.getRange(1, path.length - 1)); |
952 } | 948 } |
953 } | 949 } |
954 | 950 |
955 throw "Directory $name doesn't contain ${Strings.join(path, '/')}."; | 951 throw "Directory $name doesn't contain ${Strings.join(path, '/')}."; |
956 } | 952 } |
957 } | 953 } |
958 | 954 |
959 /// Wraps a [Future] that will complete to a [Descriptor] and makes it behave | 955 /// Wraps a [Future] that will complete to a [Descriptor] and makes it behave |
960 /// like a concrete [Descriptor]. This is necessary when the contents of the | 956 /// like a concrete [Descriptor]. This is necessary when the contents of the |
961 /// descriptor depends on information that's not available until part of the | 957 /// descriptor depends on information that's not available until part of the |
962 /// test run is completed. | 958 /// test run is completed. |
963 class FutureDescriptor extends Descriptor { | 959 class FutureDescriptor extends Descriptor { |
964 Future<Descriptor> _future; | 960 Future<Descriptor> _future; |
965 | 961 |
966 FutureDescriptor(this._future) : super('<unknown>'); | 962 FutureDescriptor(this._future) : super('<unknown>'); |
967 | 963 |
968 Future create(dir) => _future.then((desc) => desc.create(dir)); | 964 Future create(dir) => _future.then((desc) => desc.create(dir)); |
969 | 965 |
970 Future validate(dir) => _future.then((desc) => desc.validate(dir)); | 966 Future validate(dir) => _future.then((desc) => desc.validate(dir)); |
971 | 967 |
972 Future delete(dir) => _future.then((desc) => desc.delete(dir)); | 968 Future delete(dir) => _future.then((desc) => desc.delete(dir)); |
973 | 969 |
974 InputStream load(List<String> path) { | 970 ByteStream load(List<String> path) { |
975 var resultStream = new ListInputStream(); | 971 var controller = new StreamController<List<int>>(); |
976 _future.then((desc) => pipeInputToInput(desc.load(path), resultStream)); | 972 _future.then((desc) => store(desc.load(path), controller)); |
977 return resultStream; | 973 return new ByteStream(controller.stream); |
978 } | 974 } |
979 } | 975 } |
980 | 976 |
981 /// Describes a Git repository and its contents. | 977 /// Describes a Git repository and its contents. |
982 class GitRepoDescriptor extends DirectoryDescriptor { | 978 class GitRepoDescriptor extends DirectoryDescriptor { |
983 GitRepoDescriptor(Pattern name, List<Descriptor> contents) | 979 GitRepoDescriptor(Pattern name, List<Descriptor> contents) |
984 : super(name, contents); | 980 : super(name, contents); |
985 | 981 |
986 /// Creates the Git repository and commits the contents. | 982 /// Creates the Git repository and commits the contents. |
987 Future<Directory> create(parentDir) { | 983 Future<Directory> create(parentDir) { |
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1067 | 1063 |
1068 /// Creates the files and directories within this tar file, then archives | 1064 /// Creates the files and directories within this tar file, then archives |
1069 /// them, compresses them, and saves the result to [parentDir]. | 1065 /// them, compresses them, and saves the result to [parentDir]. |
1070 Future<File> create(parentDir) { | 1066 Future<File> create(parentDir) { |
1071 // TODO(rnystrom): Use withTempDir(). | 1067 // TODO(rnystrom): Use withTempDir(). |
1072 var tempDir; | 1068 var tempDir; |
1073 return createTempDir().then((_tempDir) { | 1069 return createTempDir().then((_tempDir) { |
1074 tempDir = _tempDir; | 1070 tempDir = _tempDir; |
1075 return Future.wait(contents.mappedBy((child) => child.create(tempDir))); | 1071 return Future.wait(contents.mappedBy((child) => child.create(tempDir))); |
1076 }).then((createdContents) { | 1072 }).then((createdContents) { |
1077 return consumeInputStream(createTarGz(createdContents, baseDir: tempDir)); | 1073 return createTarGz(createdContents, baseDir: tempDir).toBytes(); |
1078 }).then((bytes) { | 1074 }).then((bytes) { |
1079 return new File(join(parentDir, _stringName)).writeAsBytes(bytes); | 1075 return new File(join(parentDir, _stringName)).writeAsBytes(bytes); |
1080 }).then((file) { | 1076 }).then((file) { |
1081 return deleteDir(tempDir).then((_) => file); | 1077 return deleteDir(tempDir).then((_) => file); |
1082 }); | 1078 }); |
1083 } | 1079 } |
1084 | 1080 |
1085 /// Validates that the `.tar.gz` file at [path] contains the expected | 1081 /// Validates that the `.tar.gz` file at [path] contains the expected |
1086 /// contents. | 1082 /// contents. |
1087 Future validate(String path) { | 1083 Future validate(String path) { |
1088 throw "TODO(nweiz): implement this"; | 1084 throw "TODO(nweiz): implement this"; |
1089 } | 1085 } |
1090 | 1086 |
1091 Future delete(dir) { | 1087 Future delete(dir) { |
1092 throw new UnsupportedError(''); | 1088 throw new UnsupportedError(''); |
1093 } | 1089 } |
1094 | 1090 |
1095 /// Loads the contents of this tar file. | 1091 /// Loads the contents of this tar file. |
1096 InputStream load(List<String> path) { | 1092 ByteStream load(List<String> path) { |
1097 if (!path.isEmpty) { | 1093 if (!path.isEmpty) { |
1098 var joinedPath = Strings.join(path, '/'); | 1094 var joinedPath = Strings.join(path, '/'); |
1099 throw "Can't load $joinedPath from within $name: not a directory."; | 1095 throw "Can't load $joinedPath from within $name: not a directory."; |
1100 } | 1096 } |
1101 | 1097 |
1102 var sinkStream = new ListInputStream(); | 1098 var controller = new StreamController<List<int>>(); |
1103 var tempDir; | 1099 var tempDir; |
1104 // TODO(rnystrom): Use withTempDir() here. | 1100 // TODO(rnystrom): Use withTempDir() here. |
1105 // TODO(nweiz): propagate any errors to the return value. See issue 3657. | 1101 // TODO(nweiz): propagate any errors to the return value. See issue 3657. |
1106 createTempDir().then((_tempDir) { | 1102 createTempDir().then((_tempDir) { |
1107 tempDir = _tempDir; | 1103 tempDir = _tempDir; |
1108 return create(tempDir); | 1104 return create(tempDir); |
1109 }).then((tar) { | 1105 }).then((tar) { |
1110 var sourceStream = tar.openInputStream(); | 1106 var sourceStream = tar.openInputStream(); |
1111 return pipeInputToInput(sourceStream, sinkStream).then((_) { | 1107 return store(wrapInputStream(sourceStream), controller).then((_) { |
1112 tempDir.delete(recursive: true); | 1108 tempDir.delete(recursive: true); |
1113 }); | 1109 }); |
1114 }); | 1110 }); |
1115 return sinkStream; | 1111 return new ByteStream(controller.stream); |
1116 } | 1112 } |
1117 } | 1113 } |
1118 | 1114 |
1119 /// A descriptor that validates that no file exists with the given name. | 1115 /// A descriptor that validates that no file exists with the given name. |
1120 class NothingDescriptor extends Descriptor { | 1116 class NothingDescriptor extends Descriptor { |
1121 NothingDescriptor(String name) : super(name); | 1117 NothingDescriptor(String name) : super(name); |
1122 | 1118 |
1123 Future create(dir) => new Future.immediate(null); | 1119 Future create(dir) => new Future.immediate(null); |
1124 Future delete(dir) => new Future.immediate(null); | 1120 Future delete(dir) => new Future.immediate(null); |
1125 | 1121 |
1126 Future validate(String dir) { | 1122 Future validate(String dir) { |
1127 return exists(join(dir, name)).then((exists) { | 1123 return exists(join(dir, name)).then((exists) { |
1128 if (exists) Expect.fail('File $name in $dir should not exist.'); | 1124 if (exists) Expect.fail('File $name in $dir should not exist.'); |
1129 }); | 1125 }); |
1130 } | 1126 } |
1131 | 1127 |
1132 InputStream load(List<String> path) { | 1128 ByteStream load(List<String> path) { |
1133 if (path.isEmpty) { | 1129 if (path.isEmpty) { |
1134 throw "Can't load the contents of $name: it doesn't exist."; | 1130 throw "Can't load the contents of $name: it doesn't exist."; |
1135 } else { | 1131 } else { |
1136 throw "Can't load ${Strings.join(path, '/')} from within $name: $name " | 1132 throw "Can't load ${Strings.join(path, '/')} from within $name: $name " |
1137 "doesn't exist."; | 1133 "doesn't exist."; |
1138 } | 1134 } |
1139 } | 1135 } |
1140 } | 1136 } |
1141 | 1137 |
1142 /// A function that creates a [Validator] subclass. | 1138 /// A function that creates a [Validator] subclass. |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1193 /// Before running the test, either [shouldExit] or [kill] must be called on | 1189 /// Before running the test, either [shouldExit] or [kill] must be called on |
1194 /// this to ensure that the process terminates when expected. | 1190 /// this to ensure that the process terminates when expected. |
1195 /// | 1191 /// |
1196 /// If the test fails, this will automatically print out any remaining stdout | 1192 /// If the test fails, this will automatically print out any remaining stdout |
1197 /// and stderr from the process to aid debugging. | 1193 /// and stderr from the process to aid debugging. |
1198 class ScheduledProcess { | 1194 class ScheduledProcess { |
1199 /// The name of the process. Used for error reporting. | 1195 /// The name of the process. Used for error reporting. |
1200 final String name; | 1196 final String name; |
1201 | 1197 |
1202 /// The process future that's scheduled to run. | 1198 /// The process future that's scheduled to run. |
1203 Future<Process> _processFuture; | 1199 Future<PubProcess> _processFuture; |
1204 | 1200 |
1205 /// The process that's scheduled to run. It may be null. | 1201 /// The process that's scheduled to run. It may be null. |
1206 Process _process; | 1202 Process _process; |
1207 | 1203 |
1208 /// The exit code of the scheduled program. It may be null. | 1204 /// The exit code of the scheduled program. It may be null. |
1209 int _exitCode; | 1205 int _exitCode; |
1210 | 1206 |
1211 /// A [StringInputStream] wrapping the stdout of the process that's scheduled | 1207 /// A future that will complete to a list of all the lines emitted on the |
1212 /// to run. | 1208 /// process's standard output stream. This is independent of what data is read |
1213 final Future<StringInputStream> _stdoutFuture; | 1209 /// from [_stdout]. |
| 1210 Future<List<String>> _stdoutLines; |
1214 | 1211 |
1215 /// A [StringInputStream] wrapping the stderr of the process that's scheduled | 1212 /// A [Stream] of stdout lines emitted by the process that's scheduled to run. |
1216 /// to run. | 1213 /// It may be null. |
1217 final Future<StringInputStream> _stderrFuture; | 1214 Stream<String> _stdout; |
| 1215 |
| 1216 /// A [Future] that will resolve to [_stdout] once it's available. |
| 1217 Future get _stdoutFuture => _processFuture.then((_) => _stdout); |
| 1218 |
| 1219 /// A [StreamSubscription] that controls [_stdout]. |
| 1220 StreamSubscription _stdoutSubscription; |
| 1221 |
| 1222 /// A future that will complete to a list of all the lines emitted on the |
| 1223 /// process's standard error stream. This is independent of what data is read |
| 1224 /// from [_stderr]. |
| 1225 Future<List<String>> _stderrLines; |
| 1226 |
| 1227 /// A [Stream] of stderr lines emitted by the process that's scheduled to run. |
| 1228 /// It may be null. |
| 1229 Stream<String> _stderr; |
| 1230 |
| 1231 /// A [Future] that will resolve to [_stderr] once it's available. |
| 1232 Future get _stderrFuture => _processFuture.then((_) => _stderr); |
| 1233 |
| 1234 /// A [StreamSubscription] that controls [_stderr]. |
| 1235 StreamSubscription _stderrSubscription; |
1218 | 1236 |
1219 /// The exit code of the process that's scheduled to run. This will naturally | 1237 /// The exit code of the process that's scheduled to run. This will naturally |
1220 /// only complete once the process has terminated. | 1238 /// only complete once the process has terminated. |
1221 Future<int> get _exitCodeFuture => _exitCodeCompleter.future; | 1239 Future<int> get _exitCodeFuture => _exitCodeCompleter.future; |
1222 | 1240 |
1223 /// The completer for [_exitCode]. | 1241 /// The completer for [_exitCode]. |
1224 final Completer<int> _exitCodeCompleter = new Completer(); | 1242 final Completer<int> _exitCodeCompleter = new Completer(); |
1225 | 1243 |
1226 /// Whether the user has scheduled the end of this process by calling either | 1244 /// Whether the user has scheduled the end of this process by calling either |
1227 /// [shouldExit] or [kill]. | 1245 /// [shouldExit] or [kill]. |
1228 bool _endScheduled = false; | 1246 bool _endScheduled = false; |
1229 | 1247 |
1230 /// Whether the process is expected to terminate at this point. | 1248 /// Whether the process is expected to terminate at this point. |
1231 bool _endExpected = false; | 1249 bool _endExpected = false; |
1232 | 1250 |
1233 /// Wraps a [Process] [Future] in a scheduled process. | 1251 /// Wraps a [Process] [Future] in a scheduled process. |
1234 ScheduledProcess(this.name, Future<Process> process) | 1252 ScheduledProcess(this.name, Future<PubProcess> process) |
1235 : _processFuture = process, | 1253 : _processFuture = process { |
1236 _stdoutFuture = process.then((p) => new StringInputStream(p.stdout)), | 1254 var pairFuture = process.then((p) { |
1237 _stderrFuture = process.then((p) => new StringInputStream(p.stderr)) { | |
1238 process.then((p) { | |
1239 _process = p; | 1255 _process = p; |
| 1256 |
| 1257 var stdoutTee = tee(p.stdout.handleError((e) { |
| 1258 registerException(e.error, e.stackTrace); |
| 1259 })); |
| 1260 var stdoutPair = streamWithSubscription(stdoutTee.last); |
| 1261 _stdout = stdoutPair.first; |
| 1262 _stdoutSubscription = stdoutPair.last; |
| 1263 |
| 1264 var stderrTee = tee(p.stderr.handleError((e) { |
| 1265 registerException(e.error, e.stackTrace); |
| 1266 })); |
| 1267 var stderrPair = streamWithSubscription(stderrTee.last); |
| 1268 _stderr = stderrPair.first; |
| 1269 _stderrSubscription = stderrPair.last; |
| 1270 |
| 1271 return new Pair(stdoutTee.first, stderrTee.first); |
1240 }); | 1272 }); |
1241 | 1273 |
| 1274 _stdoutLines = pairFuture.then((pair) => pair.first.toList()); |
| 1275 _stderrLines = pairFuture.then((pair) => pair.last.toList()); |
| 1276 |
1242 _schedule((_) { | 1277 _schedule((_) { |
1243 if (!_endScheduled) { | 1278 if (!_endScheduled) { |
1244 throw new StateError("Scheduled process $name must have shouldExit() " | 1279 throw new StateError("Scheduled process $name must have shouldExit() " |
1245 "or kill() called before the test is run."); | 1280 "or kill() called before the test is run."); |
1246 } | 1281 } |
1247 | 1282 |
1248 return process.then((p) { | 1283 return process.then((p) => p.exitCode).then((exitCode) { |
1249 p.onExit = (c) { | 1284 if (_endExpected) { |
| 1285 _exitCode = exitCode; |
| 1286 _exitCodeCompleter.complete(exitCode); |
| 1287 return; |
| 1288 } |
| 1289 |
| 1290 // Sleep for half a second in case _endExpected is set in the next |
| 1291 // scheduled event. |
| 1292 sleep(500).then((_) { |
1250 if (_endExpected) { | 1293 if (_endExpected) { |
1251 _exitCode = c; | 1294 _exitCodeCompleter.complete(exitCode); |
1252 _exitCodeCompleter.complete(c); | |
1253 return; | 1295 return; |
1254 } | 1296 } |
1255 | 1297 |
1256 // Sleep for half a second in case _endExpected is set in the next | 1298 return _printStreams().then((_) { |
1257 // scheduled event. | 1299 registerException(new ExpectException("Process $name ended " |
1258 sleep(500).then((_) { | 1300 "earlier than scheduled with exit code $exitCode")); |
1259 if (_endExpected) { | |
1260 _exitCodeCompleter.complete(c); | |
1261 return; | |
1262 } | |
1263 | |
1264 _printStreams().then((_) { | |
1265 registerException(new ExpectException("Process $name ended " | |
1266 "earlier than scheduled with exit code $c")); | |
1267 }); | |
1268 }); | 1301 }); |
1269 }; | 1302 }); |
1270 }); | 1303 }); |
1271 }); | 1304 }); |
1272 | 1305 |
1273 _scheduleOnException((_) { | 1306 _scheduleOnException((_) { |
1274 if (_process == null) return; | 1307 if (_process == null) return; |
1275 | 1308 |
1276 if (_exitCode == null) { | 1309 if (_exitCode == null) { |
1277 print("\nKilling process $name prematurely."); | 1310 print("\nKilling process $name prematurely."); |
1278 _endExpected = true; | 1311 _endExpected = true; |
1279 _process.kill(); | 1312 _process.kill(); |
1280 } | 1313 } |
1281 | 1314 |
1282 return _printStreams(); | 1315 return _printStreams(); |
1283 }); | 1316 }); |
1284 | 1317 |
1285 _scheduleCleanup((_) { | 1318 _scheduleCleanup((_) { |
1286 if (_process == null) return; | 1319 if (_process == null) return; |
1287 // Ensure that the process is dead and we aren't waiting on any IO. | 1320 // Ensure that the process is dead and we aren't waiting on any IO. |
1288 _process.kill(); | 1321 _process.kill(); |
1289 _process.stdout.close(); | 1322 _stdoutSubscription.cancel(); |
1290 _process.stderr.close(); | 1323 _stderrSubscription.cancel(); |
1291 }); | 1324 }); |
1292 } | 1325 } |
1293 | 1326 |
1294 /// Reads the next line of stdout from the process. | 1327 /// Reads the next line of stdout from the process. |
1295 Future<String> nextLine() { | 1328 Future<String> nextLine() { |
1296 return _scheduleValue((_) { | 1329 return _scheduleValue((_) { |
1297 return timeout(_stdoutFuture.then((stream) => readLine(stream)), | 1330 return timeout(_stdoutFuture.then((stream) => streamFirst(stream)), |
1298 _SCHEDULE_TIMEOUT, | 1331 _SCHEDULE_TIMEOUT, |
1299 "waiting for the next stdout line from process $name"); | 1332 "waiting for the next stdout line from process $name"); |
1300 }); | 1333 }); |
1301 } | 1334 } |
1302 | 1335 |
1303 /// Reads the next line of stderr from the process. | 1336 /// Reads the next line of stderr from the process. |
1304 Future<String> nextErrLine() { | 1337 Future<String> nextErrLine() { |
1305 return _scheduleValue((_) { | 1338 return _scheduleValue((_) { |
1306 return timeout(_stderrFuture.then((stream) => readLine(stream)), | 1339 return timeout(_stderrFuture.then((stream) => streamFirst(stream)), |
1307 _SCHEDULE_TIMEOUT, | 1340 _SCHEDULE_TIMEOUT, |
1308 "waiting for the next stderr line from process $name"); | 1341 "waiting for the next stderr line from process $name"); |
1309 }); | 1342 }); |
1310 } | 1343 } |
1311 | 1344 |
1312 /// Reads the remaining stdout from the process. This should only be called | 1345 /// Reads the remaining stdout from the process. This should only be called |
1313 /// after kill() or shouldExit(). | 1346 /// after kill() or shouldExit(). |
1314 Future<String> remainingStdout() { | 1347 Future<String> remainingStdout() { |
1315 if (!_endScheduled) { | 1348 if (!_endScheduled) { |
1316 throw new StateError("remainingStdout() should only be called after " | 1349 throw new StateError("remainingStdout() should only be called after " |
1317 "kill() or shouldExit()."); | 1350 "kill() or shouldExit()."); |
1318 } | 1351 } |
1319 | 1352 |
1320 return _scheduleValue((_) { | 1353 return _scheduleValue((_) { |
1321 return timeout(_stdoutFuture.then(consumeStringInputStream), | 1354 return timeout(_stdoutFuture.then((stream) => stream.toList()) |
| 1355 .then((lines) => lines.join("\n")), |
1322 _SCHEDULE_TIMEOUT, | 1356 _SCHEDULE_TIMEOUT, |
1323 "waiting for the last stdout line from process $name"); | 1357 "waiting for the last stdout line from process $name"); |
1324 }); | 1358 }); |
1325 } | 1359 } |
1326 | 1360 |
1327 /// Reads the remaining stderr from the process. This should only be called | 1361 /// Reads the remaining stderr from the process. This should only be called |
1328 /// after kill() or shouldExit(). | 1362 /// after kill() or shouldExit(). |
1329 Future<String> remainingStderr() { | 1363 Future<String> remainingStderr() { |
1330 if (!_endScheduled) { | 1364 if (!_endScheduled) { |
1331 throw new StateError("remainingStderr() should only be called after " | 1365 throw new StateError("remainingStderr() should only be called after " |
1332 "kill() or shouldExit()."); | 1366 "kill() or shouldExit()."); |
1333 } | 1367 } |
1334 | 1368 |
1335 return _scheduleValue((_) { | 1369 return _scheduleValue((_) { |
1336 return timeout(_stderrFuture.then(consumeStringInputStream), | 1370 return timeout(_stderrFuture.then((stream) => stream.toList()) |
| 1371 .then((lines) => lines.join("\n")), |
1337 _SCHEDULE_TIMEOUT, | 1372 _SCHEDULE_TIMEOUT, |
1338 "waiting for the last stderr line from process $name"); | 1373 "waiting for the last stderr line from process $name"); |
1339 }); | 1374 }); |
1340 } | 1375 } |
1341 | 1376 |
1342 /// Writes [line] to the process as stdin. | 1377 /// Writes [line] to the process as stdin. |
1343 void writeLine(String line) { | 1378 void writeLine(String line) { |
1344 _schedule((_) => _processFuture.then( | 1379 _schedule((_) => _processFuture.then( |
1345 (p) => p.stdin.writeString('$line\n'))); | 1380 (p) => p.stdin.write('$line\n'.charCodes))); |
1346 } | 1381 } |
1347 | 1382 |
1348 /// Kills the process, and waits until it's dead. | 1383 /// Kills the process, and waits until it's dead. |
1349 void kill() { | 1384 void kill() { |
1350 _endScheduled = true; | 1385 _endScheduled = true; |
1351 _schedule((_) { | 1386 _schedule((_) { |
1352 _endExpected = true; | 1387 _endExpected = true; |
1353 _process.kill(); | 1388 _process.kill(); |
1354 timeout(_exitCodeCompleter.future, _SCHEDULE_TIMEOUT, | 1389 timeout(_exitCodeFuture, _SCHEDULE_TIMEOUT, |
1355 "waiting for process $name to die"); | 1390 "waiting for process $name to die"); |
1356 }); | 1391 }); |
1357 } | 1392 } |
1358 | 1393 |
1359 /// Waits for the process to exit, and verifies that the exit code matches | 1394 /// Waits for the process to exit, and verifies that the exit code matches |
1360 /// [expectedExitCode] (if given). | 1395 /// [expectedExitCode] (if given). |
1361 void shouldExit([int expectedExitCode]) { | 1396 void shouldExit([int expectedExitCode]) { |
1362 _endScheduled = true; | 1397 _endScheduled = true; |
1363 _schedule((_) { | 1398 _schedule((_) { |
1364 _endExpected = true; | 1399 _endExpected = true; |
1365 return timeout(_exitCodeCompleter.future, _SCHEDULE_TIMEOUT, | 1400 return timeout(_exitCodeFuture, _SCHEDULE_TIMEOUT, |
1366 "waiting for process $name to exit").then((exitCode) { | 1401 "waiting for process $name to exit").then((exitCode) { |
1367 if (expectedExitCode != null) { | 1402 if (expectedExitCode != null) { |
1368 expect(exitCode, equals(expectedExitCode)); | 1403 expect(exitCode, equals(expectedExitCode)); |
1369 } | 1404 } |
1370 }); | 1405 }); |
1371 }); | 1406 }); |
1372 } | 1407 } |
1373 | 1408 |
1374 /// Prints the remaining data in the process's stdout and stderr streams. | 1409 /// Prints the remaining data in the process's stdout and stderr streams. |
1375 /// Prints nothing if the straems are empty. | 1410 /// Prints nothing if the streams are empty. |
1376 Future _printStreams() { | 1411 Future _printStreams() { |
1377 Future printStream(String streamName, StringInputStream stream) { | 1412 void printStream(String streamName, List<String> lines) { |
1378 return consumeStringInputStream(stream).then((output) { | 1413 if (lines.isEmpty) return; |
1379 if (output.isEmpty) return; | |
1380 | 1414 |
1381 print('\nProcess $name $streamName:'); | 1415 print('\nProcess $name $streamName:'); |
1382 for (var line in output.trim().split("\n")) { | 1416 for (var line in lines) { |
1383 print('| $line'); | 1417 print('| $line'); |
1384 } | 1418 } |
1385 return; | |
1386 }); | |
1387 } | 1419 } |
1388 | 1420 |
1389 return _stdoutFuture.then((stdout) { | 1421 return _stdoutLines.then((stdoutLines) { |
1390 return _stderrFuture.then((stderr) { | 1422 printStream('stdout', stdoutLines); |
1391 return printStream('stdout', stdout) | 1423 return _stderrLines.then((stderrLines) { |
1392 .then((_) => printStream('stderr', stderr)); | 1424 printStream('stderr', stderrLines); |
1393 }); | 1425 }); |
1394 }); | 1426 }); |
1395 } | 1427 } |
1396 } | 1428 } |
1397 | 1429 |
1398 /// A class representing an [HttpServer] that's scheduled to run in the course | 1430 /// A class representing an [HttpServer] that's scheduled to run in the course |
1399 /// of the test. This class allows the server's request handling to be | 1431 /// of the test. This class allows the server's request handling to be |
1400 /// scheduled synchronously. All operations on this class are scheduled. | 1432 /// scheduled synchronously. All operations on this class are scheduled. |
1401 class ScheduledServer { | 1433 class ScheduledServer { |
1402 /// The wrapped server. | 1434 /// The wrapped server. |
(...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1539 /// calling [completion] is unnecessary. | 1571 /// calling [completion] is unnecessary. |
1540 void expectLater(Future actual, matcher, {String reason, | 1572 void expectLater(Future actual, matcher, {String reason, |
1541 FailureHandler failureHandler, bool verbose: false}) { | 1573 FailureHandler failureHandler, bool verbose: false}) { |
1542 _schedule((_) { | 1574 _schedule((_) { |
1543 return actual.then((value) { | 1575 return actual.then((value) { |
1544 expect(value, matcher, reason: reason, failureHandler: failureHandler, | 1576 expect(value, matcher, reason: reason, failureHandler: failureHandler, |
1545 verbose: false); | 1577 verbose: false); |
1546 }); | 1578 }); |
1547 }); | 1579 }); |
1548 } | 1580 } |
OLD | NEW |