Skip to content

Commit 1b3fc53

Browse files
authored
Consider all non-interactive terminals to be bots (flutter#34179)
1 parent 0bbac72 commit 1b3fc53

File tree

4 files changed

+120
-11
lines changed

4 files changed

+120
-11
lines changed

packages/flutter_tools/lib/src/base/io.dart

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
/// increase the API surface that we have to test in Flutter tools, and the APIs
2727
/// in `dart:io` can sometimes be hard to use in tests.
2828
import 'dart:async';
29-
import 'dart:io' as io show exit, IOSink, ProcessSignal, stderr, stdin, stdout;
29+
import 'dart:io' as io show exit, IOSink, ProcessSignal, stderr, stdin, Stdout, stdout;
3030

3131
import 'package:meta/meta.dart';
3232

@@ -72,6 +72,7 @@ export 'dart:io'
7272
Stdin,
7373
StdinException,
7474
// stdout, NO! Use `io.dart`
75+
Stdout,
7576
Socket,
7677
SocketException,
7778
systemEncoding,
@@ -156,7 +157,7 @@ class Stdio {
156157
const Stdio();
157158

158159
Stream<List<int>> get stdin => io.stdin;
159-
io.IOSink get stdout => io.stdout;
160+
io.Stdout get stdout => io.stdout;
160161
io.IOSink get stderr => io.stderr;
161162

162163
bool get hasTerminal => io.stdout.hasTerminal;
@@ -165,7 +166,7 @@ class Stdio {
165166
bool get supportsAnsiEscapes => hasTerminal ? io.stdout.supportsAnsiEscapes : false;
166167
}
167168

168-
Stdio get stdio => context.get<Stdio>();
169-
io.IOSink get stdout => stdio.stdout;
169+
Stdio get stdio => context.get<Stdio>() ?? const Stdio();
170+
io.Stdout get stdout => stdio.stdout;
170171
Stream<List<int>> get stdin => stdio.stdin;
171172
io.IOSink get stderr => stdio.stderr;

packages/flutter_tools/lib/src/base/utils.dart

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,20 @@ class BotDetector {
2121
const BotDetector();
2222

2323
bool get isRunningOnBot {
24-
return platform.environment['BOT'] != 'false'
25-
&& (platform.environment['BOT'] == 'true'
24+
if (
25+
// Explicitly stated to not be a bot.
26+
platform.environment['BOT'] == 'false'
27+
28+
// Set by the IDEs to the IDE name, so a strong signal that this is not a bot.
29+
|| platform.environment.containsKey('FLUTTER_HOST')
30+
) {
31+
return false;
32+
}
33+
34+
return platform.environment['BOT'] == 'true'
35+
36+
// Non-interactive terminals are assumed to be bots.
37+
|| !io.stdout.hasTerminal
2638

2739
// https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables
2840
|| platform.environment['TRAVIS'] == 'true'
@@ -43,7 +55,8 @@ class BotDetector {
4355

4456
// Properties on Flutter's Chrome Infra bots.
4557
|| platform.environment['CHROME_HEADLESS'] == '1'
46-
|| platform.environment.containsKey('BUILDBOT_BUILDERNAME'));
58+
|| platform.environment.containsKey('BUILDBOT_BUILDERNAME')
59+
|| platform.environment.containsKey('SWARMING_TASK_ID');
4760
}
4861
}
4962

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2019 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter_tools/src/base/io.dart';
6+
import 'package:flutter_tools/src/base/platform.dart';
7+
import 'package:flutter_tools/src/base/utils.dart';
8+
import 'package:platform/platform.dart';
9+
10+
import '../src/common.dart';
11+
import '../src/context.dart';
12+
import '../src/mocks.dart';
13+
14+
void main() {
15+
group('BotDetector', () {
16+
FakePlatform fakePlatform;
17+
MockStdio mockStdio;
18+
BotDetector botDetector;
19+
20+
setUp(() {
21+
fakePlatform = FakePlatform()..environment = <String, String>{};
22+
mockStdio = MockStdio();
23+
botDetector = const BotDetector();
24+
});
25+
26+
group('isRunningOnBot', () {
27+
testUsingContext('returns false unconditionally if BOT=false is set', () async {
28+
fakePlatform.environment['BOT'] = 'false';
29+
fakePlatform.environment['TRAVIS'] = 'true';
30+
expect(botDetector.isRunningOnBot, isFalse);
31+
}, overrides: <Type, Generator>{
32+
Stdio: () => mockStdio,
33+
Platform: () => fakePlatform,
34+
});
35+
36+
testUsingContext('returns false unconditionally if FLUTTER_HOST is set', () async {
37+
fakePlatform.environment['FLUTTER_HOST'] = 'foo';
38+
fakePlatform.environment['TRAVIS'] = 'true';
39+
expect(botDetector.isRunningOnBot, isFalse);
40+
}, overrides: <Type, Generator>{
41+
Stdio: () => mockStdio,
42+
Platform: () => fakePlatform,
43+
});
44+
45+
testUsingContext('returns true for non-interactive terminals', () async {
46+
mockStdio.stdout.hasTerminal = true;
47+
expect(botDetector.isRunningOnBot, isFalse);
48+
mockStdio.stdout.hasTerminal = false;
49+
expect(botDetector.isRunningOnBot, isTrue);
50+
}, overrides: <Type, Generator>{
51+
Stdio: () => mockStdio,
52+
Platform: () => fakePlatform,
53+
});
54+
});
55+
});
56+
}

packages/flutter_tools/test/src/mocks.dart

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import 'dart:async';
66
import 'dart:convert';
7-
import 'dart:io' as io show IOSink, ProcessSignal;
7+
import 'dart:io' as io show IOSink, ProcessSignal, Stdout, StdoutException;
88

99
import 'package:flutter_tools/src/android/android_device.dart';
1010
import 'package:flutter_tools/src/android/android_sdk.dart' show AndroidSdk;
@@ -342,17 +342,56 @@ class MemoryIOSink implements IOSink {
342342
Future<void> flush() async { }
343343
}
344344

345+
class MemoryStdout extends MemoryIOSink implements io.Stdout {
346+
@override
347+
bool get hasTerminal => _hasTerminal;
348+
set hasTerminal(bool value) {
349+
assert(value != null);
350+
_hasTerminal = value;
351+
}
352+
bool _hasTerminal = true;
353+
354+
@override
355+
io.IOSink get nonBlocking => this;
356+
357+
@override
358+
bool get supportsAnsiEscapes => _supportsAnsiEscapes;
359+
set supportsAnsiEscapes(bool value) {
360+
assert(value != null);
361+
_supportsAnsiEscapes = value;
362+
}
363+
bool _supportsAnsiEscapes = true;
364+
365+
@override
366+
int get terminalColumns {
367+
if (_terminalColumns != null)
368+
return _terminalColumns;
369+
throw const io.StdoutException('unspecified mock value');
370+
}
371+
set terminalColumns(int value) => _terminalColumns = value;
372+
int _terminalColumns;
373+
374+
@override
375+
int get terminalLines {
376+
if (_terminalLines != null)
377+
return _terminalLines;
378+
throw const io.StdoutException('unspecified mock value');
379+
}
380+
set terminalLines(int value) => _terminalLines = value;
381+
int _terminalLines;
382+
}
383+
345384
/// A Stdio that collects stdout and supports simulated stdin.
346385
class MockStdio extends Stdio {
347-
final MemoryIOSink _stdout = MemoryIOSink();
386+
final MemoryStdout _stdout = MemoryStdout();
348387
final MemoryIOSink _stderr = MemoryIOSink();
349388
final StreamController<List<int>> _stdin = StreamController<List<int>>();
350389

351390
@override
352-
IOSink get stdout => _stdout;
391+
MemoryStdout get stdout => _stdout;
353392

354393
@override
355-
IOSink get stderr => _stderr;
394+
MemoryIOSink get stderr => _stderr;
356395

357396
@override
358397
Stream<List<int>> get stdin => _stdin.stream;

0 commit comments

Comments
 (0)