-
Notifications
You must be signed in to change notification settings - Fork 209
/
io.dart
219 lines (195 loc) · 7.93 KB
/
io.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
// 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:convert';
import 'dart:core' as core;
import 'dart:core';
import 'dart:io';
import 'package:async/async.dart';
import 'package:path/path.dart' as p;
import 'package:test_api/src/backend/operating_system.dart'; // ignore: implementation_imports
import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
import 'pretty_print.dart';
/// The default line length for output when there isn't a terminal attached to
/// stdout.
const _defaultLineLength = 200;
/// Whether the test runner is running on Google-internal infrastructure.
final bool inGoogle = Platform.version.contains('(google3)');
/// The maximum line length for output.
final int lineLength = () {
try {
return stdout.terminalColumns;
} on UnsupportedError {
// This can throw an [UnsupportedError] if we're running in a JS context
// where `dart:io` is unavailable.
return _defaultLineLength;
} on StdoutException {
return _defaultLineLength;
}
}();
/// The root directory of the Dart SDK.
final String sdkDir = p.dirname(p.dirname(Platform.resolvedExecutable));
/// The current operating system.
final currentOS = OperatingSystem.findByIoName(Platform.operatingSystem);
/// Returns a [SuitePlatform] with the given [runtime], and with [os] and
/// [inGoogle] determined automatically.
///
/// If [runtime] is a browser, this will set [os] to [OperatingSystem.none].
SuitePlatform currentPlatform(Runtime runtime) => SuitePlatform(runtime,
os: runtime.isBrowser ? OperatingSystem.none : currentOS,
inGoogle: inGoogle);
/// A transformer that decodes bytes using UTF-8 and splits them on newlines.
final lineSplitter = StreamTransformer<List<int>, String>(
(stream, cancelOnError) => utf8.decoder
.bind(stream)
.transform(const LineSplitter())
.listen(null, cancelOnError: cancelOnError));
/// A queue of lines of standard input.
///
/// Also returns an empty stream for Fuchsia since Fuchsia components can't
/// access stdin.
StreamQueue<String> get stdinLines => _stdinLines ??= StreamQueue(
Platform.isFuchsia ? Stream<String>.empty() : lineSplitter.bind(stdin));
StreamQueue<String>? _stdinLines;
/// Call cancel on [stdinLines], but only if it's been accessed previously.
void cancelStdinLines() => _stdinLines?.cancel(immediate: true);
/// 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;
/// Whether or not the current terminal supports ansi escape codes.
///
/// Otherwise only printable ASCII characters should be used.
bool get canUseSpecialChars =>
(!Platform.isWindows || stdout.supportsAnsiEscapes) && !inTestTests;
/// Creates a temporary directory and returns its path.
String createTempDir() =>
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 Function(String) fn) {
return Future.sync(() {
var tempDir = createTempDir();
return Future.sync(() => fn(tempDir))
.whenComplete(() => Directory(tempDir).deleteSync(recursive: true));
});
}
/// Wraps [text] so that it fits within [lineLength].
///
/// This preserves existing newlines and doesn't consider terminal color escapes
/// part of a word's length. It only splits words on spaces, not on other sorts
/// of whitespace.
String wordWrap(String text) {
return text.split('\n').map((originalLine) {
var buffer = StringBuffer();
var lengthSoFar = 0;
for (var word in originalLine.split(' ')) {
var wordLength = withoutColors(word).length;
if (wordLength > lineLength) {
if (lengthSoFar != 0) buffer.writeln();
buffer.writeln(word);
} else if (lengthSoFar == 0) {
buffer.write(word);
lengthSoFar = wordLength;
} else if (lengthSoFar + 1 + wordLength > lineLength) {
buffer.writeln();
buffer.write(word);
lengthSoFar = wordLength;
} else {
buffer.write(' $word');
lengthSoFar += 1 + wordLength;
}
}
return buffer.toString();
}).join('\n');
}
/// 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].
///
/// If [print] is `true`, this prints the message using [print] to associate it
/// with the current test. Otherwise, it prints it using [stderr].
void warn(String message, {bool? color, bool print = false}) {
color ??= canUseSpecialChars;
var header = color ? '\u001b[33mWarning:\u001b[0m' : 'Warning:';
(print ? core.print : 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 extends Object>(
FutureOr<T> Function(int port) tryPort) async {
T? value;
await Future.doWhile(() async {
value = await tryPort(await getUnsafeUnusedPort());
return value == null;
});
return value!;
}
/// Whether this computer supports binding to IPv6 addresses.
var _maySupportIPv6 = true;
/// 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 {
late int port;
if (_maySupportIPv6) {
try {
final socket = await ServerSocket.bind(InternetAddress.loopbackIPv6, 0,
v6Only: true);
port = socket.port;
await socket.close();
} on SocketException {
_maySupportIPv6 = false;
}
}
if (!_maySupportIPv6) {
final socket = await RawServerSocket.bind(InternetAddress.loopbackIPv4, 0);
port = socket.port;
await socket.close();
}
return port;
}
/// Returns the full URL of the Chrome remote debugger for the main page.
///
/// This takes the [base] remote debugger URL (which points to a browser-wide
/// page) and uses its JSON API to find the resolved URL for debugging the host
/// page.
Future<Uri> getRemoteDebuggerUrl(Uri base) async {
try {
var client = HttpClient();
var request = await client.getUrl(base.resolve('/json/list'));
var response = await request.close();
var jsonObject =
await json.fuse(utf8).decoder.bind(response).single as List;
return base.resolve(jsonObject.first['devtoolsFrontendUrl'] as String);
} catch (_) {
// If we fail to talk to the remote debugger protocol, give up and return
// the raw URL rather than crashing.
return base;
}
}