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