diff --git a/packages/flutter_tools/lib/src/base/io.dart b/packages/flutter_tools/lib/src/base/io.dart index 7ae40aecfc3..9f9b8828e5d 100644 --- a/packages/flutter_tools/lib/src/base/io.dart +++ b/packages/flutter_tools/lib/src/base/io.dart @@ -26,7 +26,7 @@ /// 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. import 'dart:async'; -import 'dart:io' as io show exit, IOSink, ProcessSignal, stderr, stdin, stdout; +import 'dart:io' as io show exit, IOSink, ProcessSignal, stderr, stdin, Stdout, stdout; import 'package:meta/meta.dart'; @@ -72,6 +72,7 @@ export 'dart:io' Stdin, StdinException, // stdout, NO! Use `io.dart` + Stdout, Socket, SocketException, systemEncoding, @@ -156,7 +157,7 @@ class Stdio { const Stdio(); Stream> get stdin => io.stdin; - io.IOSink get stdout => io.stdout; + io.Stdout get stdout => io.stdout; io.IOSink get stderr => io.stderr; bool get hasTerminal => io.stdout.hasTerminal; @@ -165,7 +166,7 @@ class Stdio { bool get supportsAnsiEscapes => hasTerminal ? io.stdout.supportsAnsiEscapes : false; } -Stdio get stdio => context.get(); -io.IOSink get stdout => stdio.stdout; +Stdio get stdio => context.get() ?? const Stdio(); +io.Stdout get stdout => stdio.stdout; Stream> get stdin => stdio.stdin; io.IOSink get stderr => stdio.stderr; diff --git a/packages/flutter_tools/lib/src/base/utils.dart b/packages/flutter_tools/lib/src/base/utils.dart index 1b6d2c39cf1..9b612e20538 100644 --- a/packages/flutter_tools/lib/src/base/utils.dart +++ b/packages/flutter_tools/lib/src/base/utils.dart @@ -21,8 +21,20 @@ class BotDetector { const BotDetector(); bool get isRunningOnBot { - return platform.environment['BOT'] != 'false' - && (platform.environment['BOT'] == 'true' + if ( + // Explicitly stated to not be a bot. + platform.environment['BOT'] == 'false' + + // Set by the IDEs to the IDE name, so a strong signal that this is not a bot. + || platform.environment.containsKey('FLUTTER_HOST') + ) { + return false; + } + + return platform.environment['BOT'] == 'true' + + // Non-interactive terminals are assumed to be bots. + || !io.stdout.hasTerminal // https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables || platform.environment['TRAVIS'] == 'true' @@ -43,7 +55,8 @@ class BotDetector { // Properties on Flutter's Chrome Infra bots. || platform.environment['CHROME_HEADLESS'] == '1' - || platform.environment.containsKey('BUILDBOT_BUILDERNAME')); + || platform.environment.containsKey('BUILDBOT_BUILDERNAME') + || platform.environment.containsKey('SWARMING_TASK_ID'); } } diff --git a/packages/flutter_tools/test/base/utils_test.dart b/packages/flutter_tools/test/base/utils_test.dart new file mode 100644 index 00000000000..8c1d885531d --- /dev/null +++ b/packages/flutter_tools/test/base/utils_test.dart @@ -0,0 +1,56 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_tools/src/base/io.dart'; +import 'package:flutter_tools/src/base/platform.dart'; +import 'package:flutter_tools/src/base/utils.dart'; +import 'package:platform/platform.dart'; + +import '../src/common.dart'; +import '../src/context.dart'; +import '../src/mocks.dart'; + +void main() { + group('BotDetector', () { + FakePlatform fakePlatform; + MockStdio mockStdio; + BotDetector botDetector; + + setUp(() { + fakePlatform = FakePlatform()..environment = {}; + mockStdio = MockStdio(); + botDetector = const BotDetector(); + }); + + group('isRunningOnBot', () { + testUsingContext('returns false unconditionally if BOT=false is set', () async { + fakePlatform.environment['BOT'] = 'false'; + fakePlatform.environment['TRAVIS'] = 'true'; + expect(botDetector.isRunningOnBot, isFalse); + }, overrides: { + Stdio: () => mockStdio, + Platform: () => fakePlatform, + }); + + testUsingContext('returns false unconditionally if FLUTTER_HOST is set', () async { + fakePlatform.environment['FLUTTER_HOST'] = 'foo'; + fakePlatform.environment['TRAVIS'] = 'true'; + expect(botDetector.isRunningOnBot, isFalse); + }, overrides: { + Stdio: () => mockStdio, + Platform: () => fakePlatform, + }); + + testUsingContext('returns true for non-interactive terminals', () async { + mockStdio.stdout.hasTerminal = true; + expect(botDetector.isRunningOnBot, isFalse); + mockStdio.stdout.hasTerminal = false; + expect(botDetector.isRunningOnBot, isTrue); + }, overrides: { + Stdio: () => mockStdio, + Platform: () => fakePlatform, + }); + }); + }); +} diff --git a/packages/flutter_tools/test/src/mocks.dart b/packages/flutter_tools/test/src/mocks.dart index 8c318bc8c6d..5e935762f12 100644 --- a/packages/flutter_tools/test/src/mocks.dart +++ b/packages/flutter_tools/test/src/mocks.dart @@ -4,7 +4,7 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io' as io show IOSink, ProcessSignal; +import 'dart:io' as io show IOSink, ProcessSignal, Stdout, StdoutException; import 'package:flutter_tools/src/android/android_device.dart'; import 'package:flutter_tools/src/android/android_sdk.dart' show AndroidSdk; @@ -342,17 +342,56 @@ class MemoryIOSink implements IOSink { Future flush() async { } } +class MemoryStdout extends MemoryIOSink implements io.Stdout { + @override + bool get hasTerminal => _hasTerminal; + set hasTerminal(bool value) { + assert(value != null); + _hasTerminal = value; + } + bool _hasTerminal = true; + + @override + io.IOSink get nonBlocking => this; + + @override + bool get supportsAnsiEscapes => _supportsAnsiEscapes; + set supportsAnsiEscapes(bool value) { + assert(value != null); + _supportsAnsiEscapes = value; + } + bool _supportsAnsiEscapes = true; + + @override + int get terminalColumns { + if (_terminalColumns != null) + return _terminalColumns; + throw const io.StdoutException('unspecified mock value'); + } + set terminalColumns(int value) => _terminalColumns = value; + int _terminalColumns; + + @override + int get terminalLines { + if (_terminalLines != null) + return _terminalLines; + throw const io.StdoutException('unspecified mock value'); + } + set terminalLines(int value) => _terminalLines = value; + int _terminalLines; +} + /// A Stdio that collects stdout and supports simulated stdin. class MockStdio extends Stdio { - final MemoryIOSink _stdout = MemoryIOSink(); + final MemoryStdout _stdout = MemoryStdout(); final MemoryIOSink _stderr = MemoryIOSink(); final StreamController> _stdin = StreamController>(); @override - IOSink get stdout => _stdout; + MemoryStdout get stdout => _stdout; @override - IOSink get stderr => _stderr; + MemoryIOSink get stderr => _stderr; @override Stream> get stdin => _stdin.stream;