blob: fd2245e1986f21c8fc6f9bfa1525e4655c759ee5 [file] [log] [blame]
// Copyright (c) 2013, 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.
library utils;
import 'dart:io';
import 'dart:convert';
import 'path.dart';
// This is the maximum time we expect stdout/stderr of subprocesses to deliver
// data after we've got the exitCode.
const Duration MAX_STDIO_DELAY = const Duration(seconds: 30);
String MAX_STDIO_DELAY_PASSED_MESSAGE =
"""Not waiting for stdout/stderr from subprocess anymore
($MAX_STDIO_DELAY passed). Please note that this could be an indicator
that there is a hanging process which we were unable to kill.""";
class DebugLogger {
static IOSink _sink;
/**
* If [path] was null, the DebugLogger will write messages to stdout.
*/
static init(Path path, {append: false}) {
if (path != null) {
var mode = append ? FileMode.APPEND : FileMode.WRITE;
_sink = new File(path.toNativePath()).openWrite(mode: mode);
}
}
static void close() {
if (_sink != null) {
_sink.close();
_sink = null;
}
}
static String _formatErrorMessage(String msg, error) {
if (error == null) return msg;
msg += ": $error";
// TODO(floitsch): once the dart-executable that is bundled
// with the Dart sources is updated, pass a trace parameter too and do:
// if (trace != null) msg += "\nStackTrace: $trace";
return msg;
}
static void info(String msg, [error]) {
msg = _formatErrorMessage(msg, error);
_print("$_datetime Info: $msg");
}
static void warning(String msg, [error]) {
msg = _formatErrorMessage(msg, error);
_print("$_datetime Warning: $msg");
}
static void error(String msg, [error]) {
msg = _formatErrorMessage(msg, error);
_print("$_datetime Error: $msg");
}
static void _print(String msg) {
if (_sink != null) {
_sink.writeln(msg);
} else {
print(msg);
}
}
static String get _datetime => "${new DateTime.now()}";
}
String prettifyJson(Object json, {int startIndentation: 0, int shiftWidth: 6}) {
int currentIndentation = startIndentation;
var buffer = new StringBuffer();
String indentationString() {
return new List.filled(currentIndentation, ' ').join('');
}
addString(String s, {bool indentation: true, bool newLine: true}) {
if (indentation) {
buffer.write(indentationString());
}
buffer.write(s.replaceAll("\n", "\n${indentationString()}"));
if (newLine) buffer.write("\n");
}
prettifyJsonInternal(Object obj,
{bool indentation: true, bool newLine: true}) {
if (obj is List) {
addString("[", indentation: indentation);
currentIndentation += shiftWidth;
for (var item in obj) {
prettifyJsonInternal(item, indentation: indentation, newLine: false);
addString(",", indentation: false);
}
currentIndentation -= shiftWidth;
addString("]", indentation: indentation);
} else if (obj is Map) {
addString("{", indentation: indentation);
currentIndentation += shiftWidth;
for (var key in obj.keys) {
addString("$key: ", indentation: indentation, newLine: false);
currentIndentation += shiftWidth;
prettifyJsonInternal(obj[key], indentation: false);
currentIndentation -= shiftWidth;
}
currentIndentation -= shiftWidth;
addString("}", indentation: indentation, newLine: newLine);
} else {
addString("$obj", indentation: indentation, newLine: newLine);
}
}
prettifyJsonInternal(json);
return buffer.toString();
}
/**
* [areByteArraysEqual] compares a range of bytes from [buffer1] with a
* range of bytes from [buffer2].
*
* Returns [true] if the [count] bytes in [buffer1] (starting at
* [offset1]) match the [count] bytes in [buffer2] (starting at
* [offset2]).
* Otherwise [false] is returned.
*/
bool areByteArraysEqual(
List<int> buffer1, int offset1, List<int> buffer2, int offset2, int count) {
if ((offset1 + count) > buffer1.length ||
(offset2 + count) > buffer2.length) {
return false;
}
for (var i = 0; i < count; i++) {
if (buffer1[offset1 + i] != buffer2[offset2 + i]) {
return false;
}
}
return true;
}
/**
* [findBytes] searches for [pattern] in [data] beginning at [startPos].
*
* Returns [true] if [pattern] was found in [data].
* Otherwise [false] is returned.
*/
int findBytes(List<int> data, List<int> pattern, [int startPos = 0]) {
// TODO(kustermann): Use one of the fast string-matching algorithms!
for (int i = startPos; i < (data.length - pattern.length); i++) {
bool found = true;
for (int j = 0; j < pattern.length; j++) {
if (data[i + j] != pattern[j]) {
found = false;
break;
}
}
if (found) {
return i;
}
}
return -1;
}
List<int> encodeUtf8(String string) {
return UTF8.encode(string);
}
// TODO(kustermann,ricow): As soon we have a debug log we should log
// invalid utf8-encoded input to the log.
// Currently invalid bytes will be replaced by a replacement character.
String decodeUtf8(List<int> bytes) {
return UTF8.decode(bytes, allowMalformed: true);
}
class Locations {
static String getBrowserLocation(
String browserName, Map globalConfiguration) {
var location = globalConfiguration[browserName];
if (location != null && location != '') {
return location;
}
var browserLocations = {
'firefox': const {
'windows': 'C:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe',
'linux': 'firefox',
'macos': '/Applications/Firefox.app/Contents/MacOS/firefox'
},
'chrome': const {
'windows':
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
'macos': '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
'linux': 'google-chrome'
},
'dartium': const {
'windows': 'client\\tests\\dartium\\chrome.exe',
'macos': 'client/tests/dartium/Chromium.app/Contents/MacOS/Chromium',
'linux': 'client/tests/dartium/chrome'
},
'safari': const {
'macos': '/Applications/Safari.app/Contents/MacOS/Safari'
},
'safarimobilesim': const {
'macos': '/Applications/Xcode.app/Contents/Developer/Platforms/'
'iPhoneSimulator.platform/Developer/Applications/'
'iPhone Simulator.app/Contents/MacOS/iPhone Simulator'
},
'ie9': const {
'windows': 'C:\\Program Files\\Internet Explorer\\iexplore.exe'
},
'ie10': const {
'windows': 'C:\\Program Files\\Internet Explorer\\iexplore.exe'
},
'ie11': const {
'windows': 'C:\\Program Files\\Internet Explorer\\iexplore.exe'
}
};
browserLocations['ff'] = browserLocations['firefox'];
assert(browserLocations[browserName] != null);
location = browserLocations[browserName][Platform.operatingSystem];
if (location != null) {
return location;
} else {
throw '$browserName not supported on ${Platform.operatingSystem}';
}
}
}
// This function is pretty stupid and only puts quotes around an argument if
// it the argument contains a space.
String escapeCommandLineArgument(String argument) {
if (argument.contains(' ')) {
return '"$argument"';
}
return argument;
}
class HashCodeBuilder {
int _value = 0;
void add(Object object) {
_value = ((_value * 31) ^ object.hashCode) & 0x3FFFFFFF;
}
void addJson(Object object) {
if (object == null ||
object is num ||
object is String ||
object is Uri ||
object is bool) {
add(object);
} else if (object is List) {
object.forEach(addJson);
} else if (object is Map) {
for (var key in object.keys.toList()..sort()) {
addJson(key);
addJson(object[key]);
}
} else {
throw new Exception("Can't build hashcode for non json-like object "
"(${object.runtimeType})");
}
}
int get value => _value;
}
bool deepJsonCompare(Object a, Object b) {
if (a == null || a is num || a is String || a is Uri) {
return a == b;
} else if (a is List) {
if (b is List) {
if (a.length != b.length) return false;
for (int i = 0; i < a.length; i++) {
if (!deepJsonCompare(a[i], b[i])) return false;
}
return true;
}
return false;
} else if (a is Map) {
if (b is Map) {
if (a.length != b.length) return false;
for (var key in a.keys) {
if (!b.containsKey(key)) return false;
if (!deepJsonCompare(a[key], b[key])) return false;
}
return true;
}
return false;
} else {
throw new Exception("Can't compare two non json-like objects "
"(a: ${a.runtimeType}, b: ${b.runtimeType})");
}
}
class UniqueObject {
static int _nextId = 1;
final int _hashCode;
int get hashCode => _hashCode;
operator ==(other) => other is UniqueObject && _hashCode == other._hashCode;
UniqueObject() : _hashCode = ++_nextId;
}