io.dart 15.1 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7 8 9 10 11 12 13
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/// This file serves as the single point of entry into the `dart:io` APIs
/// within Flutter tools.
///
/// In order to make Flutter tools more testable, we use the `FileSystem` APIs
/// in `package:file` rather than using the `dart:io` file APIs directly (see
/// `file_system.dart`). Doing so allows us to swap out local file system
/// access with mockable (or in-memory) file systems, making our tests hermetic
/// vis-a-vis file system access.
///
14 15 16
/// We also use `package:platform` to provide an abstraction away from the
/// static methods in the `dart:io` `Platform` class (see `platform.dart`). As
/// such, do not export Platform from this file!
17
///
18 19 20 21 22 23 24 25 26 27
/// To ensure that all file system and platform API access within Flutter tools
/// goes through the proper APIs, we forbid direct imports of `dart:io` (via a
/// test), forcing all callers to instead import this file, which exports the
/// blessed subset of `dart:io` that is legal to use in Flutter tools.
///
/// Because of the nature of this file, it is important that **platform and file
/// APIs not be exported from `dart:io` in this file**! Moreover, be careful
/// about any additional exports that you add to this file, as doing so will
/// increase the API surface that we have to test in Flutter tools, and the APIs
/// in `dart:io` can sometimes be hard to use in tests.
28 29 30 31 32 33

// We allow `print()` in this file as a fallback for writing to the terminal via
// regular stdout/stderr/stdio paths. Everything else in the flutter_tools
// library should route terminal I/O through the [Stdio] class defined below.
// ignore_for_file: avoid_print

34
import 'dart:async';
35 36
import 'dart:io' as io
  show
37
    IOSink,
38 39 40 41 42 43 44 45 46
    InternetAddress,
    InternetAddressType,
    NetworkInterface,
    Process,
    ProcessInfo,
    ProcessSignal,
    Stdin,
    StdinException,
    Stdout,
47
    StdoutException,
48 49 50 51
    exit,
    pid,
    stderr,
    stdin,
52
    stdout;
53

54
import 'package:file/file.dart';
55
import 'package:meta/meta.dart';
56

57
import 'async_guard.dart';
58
import 'platform.dart';
59 60
import 'process.dart';

61 62 63
export 'dart:io'
    show
        BytesBuilder,
64
        CompressionOptions,
65 66 67
        // Directory,         NO! Use `file_system.dart`
        // File,              NO! Use `file_system.dart`
        // FileSystemEntity,  NO! Use `file_system.dart`
68
        GZipCodec,
69
        HandshakeException,
70 71 72
        HttpClient,
        HttpClientRequest,
        HttpClientResponse,
73
        HttpClientResponseCompressionState,
74
        HttpException,
75 76
        HttpHeaders,
        HttpRequest,
77
        HttpResponse,
78 79
        HttpServer,
        HttpStatus,
80 81
        IOException,
        IOSink,
82 83
        InternetAddress,
        InternetAddressType,
84
        // Link              NO! Use `file_system.dart`
85
        // NetworkInterface  NO! Use `io.dart`
86
        OSError,
87
        // Platform          NO! use `platform.dart`
88 89
        Process,
        ProcessException,
90
        // ProcessInfo,      NO! use `io.dart`
91
        ProcessResult,
92
        // ProcessSignal     NO! Use [ProcessSignal] below.
93
        ProcessStartMode,
94
        // RandomAccessFile  NO! Use `file_system.dart`
95
        ServerSocket,
96
        SignalException,
97 98
        Socket,
        SocketException,
99
        Stdin,
100
        StdinException,
101
        Stdout,
102
        WebSocket,
103
        WebSocketException,
104
        WebSocketTransformer,
105 106 107 108 109 110 111 112
        ZLibEncoder,
        exitCode,
        gzip,
        pid,
        // stderr,           NO! Use `io.dart`
        // stdin,            NO! Use `io.dart`
        // stdout,           NO! Use `io.dart`
        systemEncoding;
113 114

/// Exits the process with the given [exitCode].
115
typedef ExitFunction = void Function(int exitCode);
116

117
const ExitFunction _defaultExitFunction = io.exit;
118 119 120 121 122

ExitFunction _exitFunction = _defaultExitFunction;

/// Exits the process.
///
123 124 125 126
/// Throws [AssertionError] if assertions are enabled and the dart:io exit
/// is still active when called. This may indicate exit was called in
/// a test without being configured correctly.
///
127 128 129 130 131
/// This is analogous to the `exit` function in `dart:io`, except that this
/// function may be set to a testing-friendly value by calling
/// [setExitFunctionForTests] (and then restored to its default implementation
/// with [restoreExitFunction]). The default implementation delegates to
/// `dart:io`.
132 133 134 135 136 137 138 139 140 141
ExitFunction get exit {
  assert(
    _exitFunction != io.exit || !_inUnitTest(),
    'io.exit was called with assertions active in a unit test',
  );
  return _exitFunction;
}

// Whether the tool is executing in a unit test.
bool _inUnitTest() {
142
  return Zone.current[#test.declarer] != null;
143
}
144 145 146 147

/// Sets the [exit] function to a function that throws an exception rather
/// than exiting the process; this is intended for testing purposes.
@visibleForTesting
148
void setExitFunctionForTests([ ExitFunction? exitFunction ]) {
149
  _exitFunction = exitFunction ?? (int exitCode) {
150
    throw ProcessExit(exitCode, immediate: true);
151 152 153 154 155 156 157 158
  };
}

/// Restores the [exit] function to the `dart:io` implementation.
@visibleForTesting
void restoreExitFunction() {
  _exitFunction = _defaultExitFunction;
}
159 160 161 162 163 164

/// A portable version of [io.ProcessSignal].
///
/// Listening on signals that don't exist on the current platform is just a
/// no-op. This is in contrast to [io.ProcessSignal], where listening to
/// non-existent signals throws an exception.
165 166 167 168 169 170
///
/// This class does NOT implement io.ProcessSignal, because that class uses
/// private fields. This means it cannot be used with, e.g., [Process.killPid].
/// Alternative implementations of the relevant methods that take
/// [ProcessSignal] instances are available on this class (e.g. "send").
class ProcessSignal {
171
  @visibleForTesting
172 173
  const ProcessSignal(this._delegate, {@visibleForTesting Platform platform = const LocalPlatform()})
    : _platform = platform;
174

175 176 177 178 179 180
  static const ProcessSignal sigwinch = PosixProcessSignal(io.ProcessSignal.sigwinch);
  static const ProcessSignal sigterm = PosixProcessSignal(io.ProcessSignal.sigterm);
  static const ProcessSignal sigusr1 = PosixProcessSignal(io.ProcessSignal.sigusr1);
  static const ProcessSignal sigusr2 = PosixProcessSignal(io.ProcessSignal.sigusr2);
  static const ProcessSignal sigint = ProcessSignal(io.ProcessSignal.sigint);
  static const ProcessSignal sigkill = ProcessSignal(io.ProcessSignal.sigkill);
181 182

  final io.ProcessSignal _delegate;
183
  final Platform _platform;
184 185

  Stream<ProcessSignal> watch() {
186
    return _delegate.watch().map<ProcessSignal>((io.ProcessSignal signal) => this);
187 188
  }

189 190 191 192
  /// Sends the signal to the given process (identified by pid).
  ///
  /// Returns true if the signal was delivered, false otherwise.
  ///
193
  /// On Windows, this can only be used with [ProcessSignal.sigterm], which
194 195 196 197
  /// terminates the process.
  ///
  /// This is implemented by sending the signal using [Process.killPid].
  bool send(int pid) {
198
    assert(!_platform.isWindows || this == ProcessSignal.sigterm);
199 200 201
    return io.Process.killPid(pid, _delegate);
  }

202 203 204 205 206 207 208
  @override
  String toString() => _delegate.toString();
}

/// A [ProcessSignal] that is only available on Posix platforms.
///
/// Listening to a [_PosixProcessSignal] is a no-op on Windows.
209 210
@visibleForTesting
class PosixProcessSignal extends ProcessSignal {
211

212
  const PosixProcessSignal(super.wrappedSignal, {@visibleForTesting super.platform});
213 214 215

  @override
  Stream<ProcessSignal> watch() {
216 217
    // This uses the real platform since it invokes dart:io functionality directly.
    if (_platform.isWindows) {
218
      return const Stream<ProcessSignal>.empty();
219
    }
220 221 222
    return super.watch();
  }
}
223

224 225
/// A class that wraps stdout, stderr, and stdin, and exposes the allowed
/// operations.
226 227 228 229 230 231 232 233 234
///
/// In particular, there are three ways that writing to stdout and stderr
/// can fail. A call to stdout.write() can fail:
///   * by throwing a regular synchronous exception,
///   * by throwing an exception asynchronously, and
///   * by completing the Future stdout.done with an error.
///
/// This class enapsulates all three so that we don't have to worry about it
/// anywhere else.
235
class Stdio {
236 237 238 239 240 241
  Stdio();

  /// Tests can provide overrides to use instead of the stdout and stderr from
  /// dart:io.
  @visibleForTesting
  Stdio.test({
242 243
    required io.Stdout stdout,
    required io.IOSink stderr,
244 245
  }) : _stdoutOverride = stdout, _stderrOverride = stderr;

246 247
  io.Stdout? _stdoutOverride;
  io.IOSink? _stderrOverride;
248 249 250 251 252 253

  // These flags exist to remember when the done Futures on stdout and stderr
  // complete to avoid trying to write to a closed stream sink, which would
  // generate a [StateError].
  bool _stdoutDone = false;
  bool _stderrDone = false;
254 255

  Stream<List<int>> get stdin => io.stdin;
256

257 258
  io.Stdout get stdout {
    if (_stdout != null) {
259
      return _stdout!;
260 261
    }
    _stdout = _stdoutOverride ?? io.stdout;
262
    _stdout!.done.then(
263 264 265
      (void _) { _stdoutDone = true; },
      onError: (Object err, StackTrace st) { _stdoutDone = true; },
    );
266
    return _stdout!;
267
  }
268
  io.Stdout? _stdout;
269 270

  @visibleForTesting
271 272
  io.IOSink get stderr {
    if (_stderr != null) {
273
      return _stderr!;
274 275
    }
    _stderr = _stderrOverride ?? io.stderr;
276
    _stderr!.done.then(
277 278 279
      (void _) { _stderrDone = true; },
      onError: (Object err, StackTrace st) { _stderrDone = true; },
    );
280
    return _stderr!;
281
  }
282
  io.IOSink? _stderr;
283 284

  bool get hasTerminal => io.stdout.hasTerminal;
285

286
  static bool? _stdinHasTerminal;
287 288 289 290 291 292 293 294 295

  /// Determines whether there is a terminal attached.
  ///
  /// [io.Stdin.hasTerminal] only covers a subset of cases. In this check the
  /// echoMode is toggled on and off to catch cases where the tool running in
  /// a docker container thinks there is an attached terminal. This can cause
  /// runtime errors such as "inappropriate ioctl for device" if not handled.
  bool get stdinHasTerminal {
    if (_stdinHasTerminal != null) {
296
      return _stdinHasTerminal!;
297 298 299 300
    }
    if (stdin is! io.Stdin) {
      return _stdinHasTerminal = false;
    }
301
    final io.Stdin ioStdin = stdin as io.Stdin;
302 303 304 305 306 307 308 309 310 311 312 313 314
    if (!ioStdin.hasTerminal) {
      return _stdinHasTerminal = false;
    }
    try {
      final bool currentEchoMode = ioStdin.echoMode;
      ioStdin.echoMode = !currentEchoMode;
      ioStdin.echoMode = currentEchoMode;
    } on io.StdinException {
      return _stdinHasTerminal = false;
    }
    return _stdinHasTerminal = true;
  }

315 316
  int? get terminalColumns => hasTerminal ? stdout.terminalColumns : null;
  int? get terminalLines => hasTerminal ? stdout.terminalLines : null;
317
  bool get supportsAnsiEscapes => hasTerminal && stdout.supportsAnsiEscapes;
318

319 320 321 322
  /// Writes [message] to [stderr], falling back on [fallback] if the write
  /// throws any exception. The default fallback calls [print] on [message].
  void stderrWrite(
    String message, {
323
    void Function(String, dynamic, StackTrace)? fallback,
324 325 326 327 328 329 330 331 332 333 334
  }) {
    if (!_stderrDone) {
      _stdioWrite(stderr, message, fallback: fallback);
      return;
    }
    fallback == null ? print(message) : fallback(
      message,
      const io.StdoutException('stderr is done'),
      StackTrace.current,
    );
  }
335 336 337 338 339

  /// Writes [message] to [stdout], falling back on [fallback] if the write
  /// throws any exception. The default fallback calls [print] on [message].
  void stdoutWrite(
    String message, {
340
    void Function(String, dynamic, StackTrace)? fallback,
341 342 343 344 345 346 347 348 349 350 351
  }) {
    if (!_stdoutDone) {
      _stdioWrite(stdout, message, fallback: fallback);
      return;
    }
    fallback == null ? print(message) : fallback(
      message,
      const io.StdoutException('stdout is done'),
      StackTrace.current,
    );
  }
352

353
  // Helper for [stderrWrite] and [stdoutWrite].
354
  void _stdioWrite(io.IOSink sink, String message, {
355
    void Function(String, dynamic, StackTrace)? fallback,
356
  }) {
357
    asyncGuard<void>(() async {
358
      sink.write(message);
359
    }, onError: (Object error, StackTrace stackTrace) {
360 361 362
      if (fallback == null) {
        print(message);
      } else {
363
        fallback(message, error, stackTrace);
364
      }
365
    });
366
  }
367 368 369 370

  /// Adds [stream] to [stdout].
  Future<void> addStdoutStream(Stream<List<int>> stream) => stdout.addStream(stream);

371
  /// Adds [stream] to [stderr].
372
  Future<void> addStderrStream(Stream<List<int>> stream) => stderr.addStream(stream);
373 374
}

375 376
/// An overridable version of io.ProcessInfo.
abstract class ProcessInfo {
377
  factory ProcessInfo(FileSystem fs) => _DefaultProcessInfo(fs);
378

379
  factory ProcessInfo.test(FileSystem fs) => _TestProcessInfo(fs);
380 381 382 383

  int get currentRss;

  int get maxRss;
384 385

  File writePidFile(String pidFile);
386 387 388 389
}

/// The default implementation of [ProcessInfo], which uses [io.ProcessInfo].
class _DefaultProcessInfo implements ProcessInfo {
390 391 392 393
  _DefaultProcessInfo(this._fileSystem);

  final FileSystem _fileSystem;

394 395 396 397 398
  @override
  int get currentRss => io.ProcessInfo.currentRss;

  @override
  int get maxRss => io.ProcessInfo.maxRss;
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425

  @override
  File writePidFile(String pidFile) {
    assert(pidFile != null);
    return _fileSystem.file(pidFile)
      ..writeAsStringSync(io.pid.toString());
  }
}

/// The test version of [ProcessInfo].
class _TestProcessInfo implements ProcessInfo {
  _TestProcessInfo(this._fileSystem);

  final FileSystem _fileSystem;

  @override
  int currentRss = 1000;

  @override
  int maxRss = 2000;

  @override
  File writePidFile(String pidFile) {
    assert(pidFile != null);
    return _fileSystem.file(pidFile)
      ..writeAsStringSync('12345');
  }
426
}
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452

/// The return type for [listNetworkInterfaces].
class NetworkInterface implements io.NetworkInterface {
  NetworkInterface(this._delegate);

  final io.NetworkInterface _delegate;

  @override
  List<io.InternetAddress> get addresses => _delegate.addresses;

  @override
  int get index => _delegate.index;

  @override
  String get name => _delegate.name;

  @override
  String toString() => "NetworkInterface('$name', $addresses)";
}

typedef NetworkInterfaceLister = Future<List<NetworkInterface>> Function({
  bool includeLoopback,
  bool includeLinkLocal,
  io.InternetAddressType type,
});

453
NetworkInterfaceLister? _networkInterfaceListerOverride;
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474

// Tests can set up a non-default network interface lister.
@visibleForTesting
void setNetworkInterfaceLister(NetworkInterfaceLister lister) {
  _networkInterfaceListerOverride = lister;
}

@visibleForTesting
void resetNetworkInterfaceLister() {
  _networkInterfaceListerOverride = null;
}

/// This calls [NetworkInterface.list] from `dart:io` unless it is overridden by
/// [setNetworkInterfaceLister] for a test. If it is overridden for a test,
/// it should be reset with [resetNetworkInterfaceLister].
Future<List<NetworkInterface>> listNetworkInterfaces({
  bool includeLoopback = false,
  bool includeLinkLocal = false,
  io.InternetAddressType type = io.InternetAddressType.any,
}) async {
  if (_networkInterfaceListerOverride != null) {
475
    return _networkInterfaceListerOverride!.call(
476 477 478 479 480 481 482 483 484 485 486 487 488 489
      includeLoopback: includeLoopback,
      includeLinkLocal: includeLinkLocal,
      type: type,
    );
  }
  final List<io.NetworkInterface> interfaces = await io.NetworkInterface.list(
    includeLoopback: includeLoopback,
    includeLinkLocal: includeLinkLocal,
    type: type,
  );
  return interfaces.map(
    (io.NetworkInterface interface) => NetworkInterface(interface),
  ).toList();
}