OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 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. |
| 4 |
| 5 library test.util.io; |
| 6 |
| 7 import 'dart:async'; |
| 8 import 'dart:convert'; |
| 9 import 'dart:io'; |
| 10 import 'dart:mirrors'; |
| 11 |
| 12 import 'package:path/path.dart' as p; |
| 13 import 'package:pub_semver/pub_semver.dart'; |
| 14 |
| 15 import '../backend/operating_system.dart'; |
| 16 import '../runner/application_exception.dart'; |
| 17 import '../util/stream_queue.dart'; |
| 18 import '../utils.dart'; |
| 19 |
| 20 /// The ASCII code for a newline character. |
| 21 const _newline = 0xA; |
| 22 |
| 23 /// The ASCII code for a carriage return character. |
| 24 const _carriageReturn = 0xD; |
| 25 |
| 26 /// The root directory of the Dart SDK. |
| 27 final String sdkDir = p.dirname(p.dirname(Platform.resolvedExecutable)); |
| 28 |
| 29 /// The version of the Dart SDK currently in use. |
| 30 final Version _sdkVersion = new Version.parse( |
| 31 new File(p.join(sdkDir, 'version')) |
| 32 .readAsStringSync().trim()); |
| 33 |
| 34 /// Returns the current operating system. |
| 35 final OperatingSystem currentOS = (() { |
| 36 var name = Platform.operatingSystem; |
| 37 var os = OperatingSystem.findByIoName(name); |
| 38 if (os != null) return os; |
| 39 |
| 40 throw new UnsupportedError('Unsupported operating system "$name".'); |
| 41 })(); |
| 42 |
| 43 /// A queue of lines of standard input. |
| 44 final stdinLines = new StreamQueue( |
| 45 UTF8.decoder.fuse(const LineSplitter()).bind(stdin)); |
| 46 |
| 47 /// The root directory below which to nest temporary directories created by the |
| 48 /// test runner. |
| 49 /// |
| 50 /// This is configurable so that the test code can validate that the runner |
| 51 /// cleans up after itself fully. |
| 52 final _tempDir = Platform.environment.containsKey("_UNITTEST_TEMP_DIR") |
| 53 ? Platform.environment["_UNITTEST_TEMP_DIR"] |
| 54 : Directory.systemTemp.path; |
| 55 |
| 56 /// The path to the `lib` directory of the `test` package. |
| 57 String libDir({String packageRoot}) { |
| 58 var pathToIo = libraryPath(#test.util.io, packageRoot: packageRoot); |
| 59 return p.dirname(p.dirname(p.dirname(pathToIo))); |
| 60 } |
| 61 |
| 62 // TODO(nweiz): Make this check [stdioType] once that works within "pub run". |
| 63 /// Whether "special" strings such as Unicode characters or color escapes are |
| 64 /// safe to use. |
| 65 /// |
| 66 /// On Windows or when not printing to a terminal, only printable ASCII |
| 67 /// characters should be used. |
| 68 bool get canUseSpecialChars => |
| 69 Platform.operatingSystem != 'windows' && |
| 70 Platform.environment["_UNITTEST_USE_COLOR"] != "false"; |
| 71 |
| 72 /// Creates a temporary directory and returns its path. |
| 73 String createTempDir() => |
| 74 new Directory(_tempDir).createTempSync('dart_test_').path; |
| 75 |
| 76 /// Creates a temporary directory and passes its path to [fn]. |
| 77 /// |
| 78 /// Once the [Future] returned by [fn] completes, the temporary directory and |
| 79 /// all its contents are deleted. [fn] can also return `null`, in which case |
| 80 /// the temporary directory is deleted immediately afterwards. |
| 81 /// |
| 82 /// Returns a future that completes to the value that the future returned from |
| 83 /// [fn] completes to. |
| 84 Future withTempDir(Future fn(String path)) { |
| 85 return new Future.sync(() { |
| 86 var tempDir = createTempDir(); |
| 87 return new Future.sync(() => fn(tempDir)) |
| 88 .whenComplete(() => new Directory(tempDir).deleteSync(recursive: true)); |
| 89 }); |
| 90 } |
| 91 |
| 92 /// Return a transformation of [input] with all null bytes removed. |
| 93 /// |
| 94 /// This works around the combination of issue 23295 and 22667 by removing null |
| 95 /// bytes. This workaround can be removed when either of those are fixed in the |
| 96 /// oldest supported SDK. |
| 97 /// |
| 98 /// It also somewhat works around issue 23303 by removing any carriage returns |
| 99 /// that are followed by newlines, to ensure that carriage returns aren't |
| 100 /// doubled up in the output. This can be removed when the issue is fixed in the |
| 101 /// oldest supported SDk. |
| 102 Stream<List<int>> sanitizeForWindows(Stream<List<int>> input) { |
| 103 if (!Platform.isWindows) return input; |
| 104 |
| 105 return input.map((list) { |
| 106 var previous; |
| 107 return list.reversed.where((byte) { |
| 108 if (byte == 0) return false; |
| 109 if (byte == _carriageReturn && previous == _newline) return false; |
| 110 previous = byte; |
| 111 return true; |
| 112 }).toList().reversed.toList(); |
| 113 }); |
| 114 } |
| 115 |
| 116 /// Print a warning containing [message]. |
| 117 /// |
| 118 /// This automatically wraps lines if they get too long. If [color] is passed, |
| 119 /// it controls whether the warning header is color; otherwise, it defaults to |
| 120 /// [canUseSpecialChars]. |
| 121 void warn(String message, {bool color}) { |
| 122 if (color == null) color = canUseSpecialChars; |
| 123 var header = color |
| 124 ? "\u001b[33mWarning:\u001b[0m" |
| 125 : "Warning:"; |
| 126 stderr.writeln(wordWrap("$header $message\n")); |
| 127 } |
| 128 |
| 129 /// Creates a URL string for [address]:[port]. |
| 130 /// |
| 131 /// Handles properly formatting IPv6 addresses. |
| 132 Uri baseUrlForAddress(InternetAddress address, int port) { |
| 133 if (address.isLoopback) { |
| 134 return new Uri(scheme: "http", host: "localhost", port: port); |
| 135 } |
| 136 |
| 137 // IPv6 addresses in URLs need to be enclosed in square brackets to avoid |
| 138 // URL ambiguity with the ":" in the address. |
| 139 if (address.type == InternetAddressType.IP_V6) { |
| 140 return new Uri(scheme: "http", host: "[${address.address}]", port: port); |
| 141 } |
| 142 |
| 143 return new Uri(scheme: "http", host: address.address, port: port); |
| 144 } |
| 145 |
| 146 /// Returns the package root at [root]. |
| 147 /// |
| 148 /// If [override] is passed, that's used. If the package root doesn't exist, an |
| 149 /// [ApplicationException] is thrown. |
| 150 String packageRootFor(String root, [String override]) { |
| 151 if (root == null) root = p.current; |
| 152 var packageRoot = override == null ? p.join(root, 'packages') : override; |
| 153 |
| 154 if (!new Directory(packageRoot).existsSync()) { |
| 155 throw new ApplicationException( |
| 156 "Directory ${p.prettyUri(p.toUri(packageRoot))} does not exist."); |
| 157 } |
| 158 |
| 159 return packageRoot; |
| 160 } |
| 161 |
| 162 /// The library name must be globally unique, or the wrong library path may be |
| 163 /// returned. |
| 164 String libraryPath(Symbol libraryName, {String packageRoot}) { |
| 165 var lib = currentMirrorSystem().findLibrary(libraryName); |
| 166 if (lib.uri.scheme != 'package') return p.fromUri(lib.uri); |
| 167 |
| 168 // TODO(nweiz): is there a way to avoid assuming this is being run next to a |
| 169 // packages directory?. |
| 170 if (packageRoot == null) packageRoot = p.absolute('packages'); |
| 171 return p.join(packageRoot, p.fromUri(lib.uri.path)); |
| 172 } |
| 173 |
| 174 /// Repeatedly finds a probably-unused port on localhost and passes it to |
| 175 /// [tryPort] until it binds successfully. |
| 176 /// |
| 177 /// [tryPort] should return a non-`null` value or a Future completing to a |
| 178 /// non-`null` value once it binds successfully. This value will be returned |
| 179 /// by [getUnusedPort] in turn. |
| 180 /// |
| 181 /// This is necessary for ensuring that our port binding isn't flaky for |
| 182 /// applications that don't print out the bound port. |
| 183 Future getUnusedPort(tryPort(int port)) { |
| 184 var value; |
| 185 return Future.doWhile(() async { |
| 186 value = await tryPort(await getUnsafeUnusedPort()); |
| 187 return value == null; |
| 188 }).then((_) => value); |
| 189 } |
| 190 |
| 191 /// Returns a port that is probably, but not definitely, not in use. |
| 192 /// |
| 193 /// This has a built-in race condition: another process may bind this port at |
| 194 /// any time after this call has returned. If at all possible, callers should |
| 195 /// use [getUnusedPort] instead. |
| 196 Future<int> getUnsafeUnusedPort() async { |
| 197 var socket = await RawServerSocket.bind(InternetAddress.LOOPBACK_IP_V4, 0); |
| 198 var port = socket.port; |
| 199 await socket.close(); |
| 200 return port; |
| 201 } |
OLD | NEW |