Unverified Commit 016ca1a0 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Catch io.StdinException from failure to set stdin echo/line mode (#43225)

parent 79a985f9
......@@ -27,7 +27,7 @@
/// in `dart:io` can sometimes be hard to use in tests.
import 'dart:async';
import 'dart:io' as io show exit, IOSink, Process, ProcessInfo, ProcessSignal,
stderr, stdin, Stdout, stdout;
stderr, stdin, Stdin, StdinException, Stdout, stdout;
import 'package:meta/meta.dart';
......@@ -181,6 +181,36 @@ class Stdio {
io.IOSink get stderr => io.stderr;
bool get hasTerminal => io.stdout.hasTerminal;
static bool _stdinHasTerminal;
/// 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) {
return _stdinHasTerminal;
}
if (stdin is! io.Stdin) {
return _stdinHasTerminal = false;
}
final io.Stdin ioStdin = stdin;
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;
}
int get terminalColumns => hasTerminal ? io.stdout.terminalColumns : null;
int get terminalLines => hasTerminal ? io.stdout.terminalLines : null;
bool get supportsAnsiEscapes => hasTerminal && io.stdout.supportsAnsiEscapes;
......@@ -190,6 +220,7 @@ Stdio get stdio => context.get<Stdio>() ?? const Stdio();
io.Stdout get stdout => stdio.stdout;
Stream<List<int>> get stdin => stdio.stdin;
io.IOSink get stderr => stdio.stderr;
bool get stdinHasTerminal => stdio.stdinHasTerminal;
/// An overridable version of io.ProcessInfo.
abstract class ProcessInfo {
......
......@@ -155,16 +155,17 @@ class AnsiTerminal {
String clearScreen() => supportsColor ? clear : '\n\n';
set singleCharMode(bool value) {
final Stream<List<int>> stdin = io.stdin;
if (stdin is io.Stdin && stdin.hasTerminal) {
// The order of setting lineMode and echoMode is important on Windows.
if (value) {
stdin.echoMode = false;
stdin.lineMode = false;
} else {
stdin.lineMode = true;
stdin.echoMode = true;
}
if (!io.stdinHasTerminal) {
return;
}
final io.Stdin stdin = io.stdin;
// The order of setting lineMode and echoMode is important on Windows.
if (value) {
stdin.echoMode = false;
stdin.lineMode = false;
} else {
stdin.lineMode = true;
stdin.echoMode = true;
}
}
......
......@@ -4,9 +4,11 @@
import 'dart:async';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/globals.dart';
import 'package:mockito/mockito.dart';
import '../../src/common.dart';
import '../../src/context.dart';
......@@ -167,6 +169,15 @@ void main() {
'Please choose something: \n'
'\n');
});
testUsingContext('Does not set single char mode when a terminal is not attached', () {
when(stdio.stdin).thenThrow(StateError('This should not be called'));
when(stdio.stdinHasTerminal).thenReturn(false);
terminal.singleCharMode = true;
}, overrides: <Type, Generator>{
Stdio: () => MockStdio(),
});
});
}
......@@ -178,3 +189,5 @@ class TestTerminal extends AnsiTerminal {
return mockStdInStream;
}
}
class MockStdio extends Mock implements Stdio {}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment