blob: 3f7f13a6736d7d3697d8ac2f3961d29f88bb56b0 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:pub_semver/pub_semver.dart';
import '../backend/operating_system.dart';
import '../util/stream_queue.dart';
import '../utils.dart';
/// The ASCII code for a newline character.
const _newline = 0xA;
/// The ASCII code for a carriage return character.
const _carriageReturn = 0xD;
/// The root directory of the Dart SDK.
final String sdkDir = p.dirname(p.dirname(Platform.resolvedExecutable));
/// The version of the Dart SDK currently in use.
final Version _sdkVersion = new Version.parse(
new File(p.join(sdkDir, 'version'))
.readAsStringSync().trim());
/// Returns the current operating system.
final OperatingSystem currentOS = (() {
var name = Platform.operatingSystem;
var os = OperatingSystem.findByIoName(name);
if (os != null) return os;
throw new UnsupportedError('Unsupported operating system "$name".');
})();
/// A queue of lines of standard input.
final stdinLines = new StreamQueue(lineSplitter.bind(stdin));
/// Whether this is being run as a subprocess in the test package's own tests.
bool inTestTests = Platform.environment["_DART_TEST_TESTING"] == "true";
/// The root directory below which to nest temporary directories created by the
/// test runner.
///
/// This is configurable so that the test code can validate that the runner
/// cleans up after itself fully.
final _tempDir = Platform.environment.containsKey("_UNITTEST_TEMP_DIR")
? Platform.environment["_UNITTEST_TEMP_DIR"]
: Directory.systemTemp.path;
// TODO(nweiz): Make this check [stdioType] once that works within "pub run".
/// Whether "special" strings such as Unicode characters or color escapes are
/// safe to use.
///
/// On Windows or when not printing to a terminal, only printable ASCII
/// characters should be used.
bool get canUseSpecialChars =>
Platform.operatingSystem != 'windows' && !inTestTests;
/// Creates a temporary directory and returns its path.
String createTempDir() => new Directory(_tempDir)
.createTempSync('dart_test_').resolveSymbolicLinksSync();
/// Creates a temporary directory and passes its path to [fn].
///
/// Once the [Future] returned by [fn] completes, the temporary directory and
/// all its contents are deleted. [fn] can also return `null`, in which case
/// the temporary directory is deleted immediately afterwards.
///
/// Returns a future that completes to the value that the future returned from
/// [fn] completes to.
Future withTempDir(Future fn(String path)) {
return new Future.sync(() {
var tempDir = createTempDir();
return new Future.sync(() => fn(tempDir))
.whenComplete(() => new Directory(tempDir).deleteSync(recursive: true));
});
}
/// Return a transformation of [input] with all null bytes removed.
///
/// This works around the combination of issue 23295 and 22667 by removing null
/// bytes. This workaround can be removed when either of those are fixed in the
/// oldest supported SDK.
///
/// It also somewhat works around issue 23303 by removing any carriage returns
/// that are followed by newlines, to ensure that carriage returns aren't
/// doubled up in the output. This can be removed when the issue is fixed in the
/// oldest supported SDk.
Stream<List<int>> sanitizeForWindows(Stream<List<int>> input) {
if (!Platform.isWindows) return input;
return input.map((list) {
var previous;
return list.reversed.where((byte) {
if (byte == 0) return false;
if (byte == _carriageReturn && previous == _newline) return false;
previous = byte;
return true;
}).toList().reversed.toList();
});
}
/// Print a warning containing [message].
///
/// This automatically wraps lines if they get too long. If [color] is passed,
/// it controls whether the warning header is color; otherwise, it defaults to
/// [canUseSpecialChars].
void warn(String message, {bool color}) {
if (color == null) color = canUseSpecialChars;
var header = color
? "\u001b[33mWarning:\u001b[0m"
: "Warning:";
stderr.writeln(wordWrap("$header $message\n"));
}
/// Repeatedly finds a probably-unused port on localhost and passes it to
/// [tryPort] until it binds successfully.
///
/// [tryPort] should return a non-`null` value or a Future completing to a
/// non-`null` value once it binds successfully. This value will be returned
/// by [getUnusedPort] in turn.
///
/// This is necessary for ensuring that our port binding isn't flaky for
/// applications that don't print out the bound port.
Future/*<T>*/ getUnusedPort/*<T>*/(/*=T*/ tryPort(int port)) {
var/*=T*/ value;
return Future.doWhile(() async {
value = await tryPort(await getUnsafeUnusedPort());
return value == null;
}).then((_) => value);
}
/// Returns a port that is probably, but not definitely, not in use.
///
/// This has a built-in race condition: another process may bind this port at
/// any time after this call has returned. If at all possible, callers should
/// use [getUnusedPort] instead.
Future<int> getUnsafeUnusedPort() async {
var socket = await RawServerSocket.bind(InternetAddress.LOOPBACK_IP_V4, 0);
var port = socket.port;
await socket.close();
return port;
}