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