mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
[flutter_tools] cache result of BotDetector in persistent tool state (#52325)
The Azure bot detection can take up to a second to determine if a client is/isn't a bot. To prevent this from slowing down all flutter commands, we can cache the results in the persistent tool state - since we don't expect the same client id to ever become a bot or stop being a bot
This commit is contained in:
parent
377879825e
commit
2133343a29
|
@ -35,8 +35,6 @@ Future<int> run(
|
||||||
String flutterVersion,
|
String flutterVersion,
|
||||||
Map<Type, Generator> overrides,
|
Map<Type, Generator> overrides,
|
||||||
}) async {
|
}) async {
|
||||||
reportCrashes ??= !await globals.isRunningOnBot;
|
|
||||||
|
|
||||||
if (muteCommandLogging) {
|
if (muteCommandLogging) {
|
||||||
// Remove the verbose option; for help and doctor, users don't need to see
|
// Remove the verbose option; for help and doctor, users don't need to see
|
||||||
// verbose logs.
|
// verbose logs.
|
||||||
|
@ -48,6 +46,8 @@ Future<int> run(
|
||||||
commands.forEach(runner.addCommand);
|
commands.forEach(runner.addCommand);
|
||||||
|
|
||||||
return runInContext<int>(() async {
|
return runInContext<int>(() async {
|
||||||
|
reportCrashes ??= !await globals.isRunningOnBot;
|
||||||
|
|
||||||
// Initialize the system locale.
|
// Initialize the system locale.
|
||||||
final String systemLocale = await intl_standalone.findSystemLocale();
|
final String systemLocale = await intl_standalone.findSystemLocale();
|
||||||
intl.Intl.defaultLocale = intl.Intl.verifiedLocale(
|
intl.Intl.defaultLocale = intl.Intl.verifiedLocale(
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:platform/platform.dart';
|
import 'package:platform/platform.dart';
|
||||||
|
|
||||||
|
import '../persistent_tool_state.dart';
|
||||||
import 'io.dart';
|
import 'io.dart';
|
||||||
import 'net.dart';
|
import 'net.dart';
|
||||||
|
|
||||||
|
@ -12,20 +13,21 @@ class BotDetector {
|
||||||
BotDetector({
|
BotDetector({
|
||||||
@required HttpClientFactory httpClientFactory,
|
@required HttpClientFactory httpClientFactory,
|
||||||
@required Platform platform,
|
@required Platform platform,
|
||||||
|
@required PersistentToolState persistentToolState,
|
||||||
}) :
|
}) :
|
||||||
_platform = platform,
|
_platform = platform,
|
||||||
_azureDetector = AzureDetector(
|
_azureDetector = AzureDetector(
|
||||||
httpClientFactory: httpClientFactory,
|
httpClientFactory: httpClientFactory,
|
||||||
);
|
),
|
||||||
|
_persistentToolState = persistentToolState;
|
||||||
|
|
||||||
final Platform _platform;
|
final Platform _platform;
|
||||||
final AzureDetector _azureDetector;
|
final AzureDetector _azureDetector;
|
||||||
|
final PersistentToolState _persistentToolState;
|
||||||
bool _isRunningOnBot;
|
|
||||||
|
|
||||||
Future<bool> get isRunningOnBot async {
|
Future<bool> get isRunningOnBot async {
|
||||||
if (_isRunningOnBot != null) {
|
if (_persistentToolState.isRunningOnBot != null) {
|
||||||
return _isRunningOnBot;
|
return _persistentToolState.isRunningOnBot;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
// Explicitly stated to not be a bot.
|
// Explicitly stated to not be a bot.
|
||||||
|
@ -36,10 +38,10 @@ class BotDetector {
|
||||||
// When set, GA logs to a local file (normally for tests) so we don't need to filter.
|
// When set, GA logs to a local file (normally for tests) so we don't need to filter.
|
||||||
|| _platform.environment.containsKey('FLUTTER_ANALYTICS_LOG_FILE')
|
|| _platform.environment.containsKey('FLUTTER_ANALYTICS_LOG_FILE')
|
||||||
) {
|
) {
|
||||||
return _isRunningOnBot = false;
|
return _persistentToolState.isRunningOnBot = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _isRunningOnBot = _platform.environment['BOT'] == 'true'
|
return _persistentToolState.isRunningOnBot = _platform.environment['BOT'] == 'true'
|
||||||
|
|
||||||
// https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables
|
// https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables
|
||||||
|| _platform.environment['TRAVIS'] == 'true'
|
|| _platform.environment['TRAVIS'] == 'true'
|
||||||
|
@ -94,7 +96,7 @@ class AzureDetector {
|
||||||
return _isRunningOnAzure;
|
return _isRunningOnAzure;
|
||||||
}
|
}
|
||||||
final HttpClient client = _httpClientFactory()
|
final HttpClient client = _httpClientFactory()
|
||||||
..connectionTimeout = const Duration(seconds: 1);
|
..connectionTimeout = const Duration(milliseconds: 250);
|
||||||
try {
|
try {
|
||||||
final HttpClientRequest request = await client.getUrl(
|
final HttpClientRequest request = await client.getUrl(
|
||||||
Uri.parse(_serviceUrl),
|
Uri.parse(_serviceUrl),
|
||||||
|
|
|
@ -80,6 +80,7 @@ XCDevice get xcdevice => context.get<XCDevice>();
|
||||||
final BotDetector _defaultBotDetector = BotDetector(
|
final BotDetector _defaultBotDetector = BotDetector(
|
||||||
httpClientFactory: context.get<HttpClientFactory>() ?? () => HttpClient(),
|
httpClientFactory: context.get<HttpClientFactory>() ?? () => HttpClient(),
|
||||||
platform: platform,
|
platform: platform,
|
||||||
|
persistentToolState: persistentToolState,
|
||||||
);
|
);
|
||||||
|
|
||||||
BotDetector get botDetector => context.get<BotDetector>() ?? _defaultBotDetector;
|
BotDetector get botDetector => context.get<BotDetector>() ?? _defaultBotDetector;
|
||||||
|
|
|
@ -46,6 +46,9 @@ abstract class PersistentToolState {
|
||||||
|
|
||||||
/// Update the last active version for a given [channel].
|
/// Update the last active version for a given [channel].
|
||||||
void updateLastActiveVersion(String fullGitHash, Channel channel);
|
void updateLastActiveVersion(String fullGitHash, Channel channel);
|
||||||
|
|
||||||
|
/// Whether this client was already determined to be or not be a bot.
|
||||||
|
bool isRunningOnBot;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DefaultPersistentToolState implements PersistentToolState {
|
class _DefaultPersistentToolState implements PersistentToolState {
|
||||||
|
@ -78,6 +81,7 @@ class _DefaultPersistentToolState implements PersistentToolState {
|
||||||
Channel.beta: 'last-active-beta-version',
|
Channel.beta: 'last-active-beta-version',
|
||||||
Channel.stable: 'last-active-stable-version'
|
Channel.stable: 'last-active-stable-version'
|
||||||
};
|
};
|
||||||
|
static const String _kBotKey = 'is-bot';
|
||||||
|
|
||||||
final Config _config;
|
final Config _config;
|
||||||
|
|
||||||
|
@ -108,4 +112,10 @@ class _DefaultPersistentToolState implements PersistentToolState {
|
||||||
String _versionKeyFor(Channel channel) {
|
String _versionKeyFor(Channel channel) {
|
||||||
return _lastActiveVersionKeys[channel];
|
return _lastActiveVersionKeys[channel];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isRunningOnBot => _config.getValue(_kBotKey) as bool;
|
||||||
|
|
||||||
|
@override
|
||||||
|
set isRunningOnBot(bool value) => _config.setValue(_kBotKey, value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,11 @@
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:file/memory.dart';
|
||||||
import 'package:flutter_tools/src/base/bot_detector.dart';
|
import 'package:flutter_tools/src/base/bot_detector.dart';
|
||||||
import 'package:flutter_tools/src/base/io.dart';
|
import 'package:flutter_tools/src/base/io.dart';
|
||||||
|
import 'package:flutter_tools/src/base/logger.dart';
|
||||||
|
import 'package:flutter_tools/src/persistent_tool_state.dart';
|
||||||
import 'package:mockito/mockito.dart';
|
import 'package:mockito/mockito.dart';
|
||||||
import 'package:platform/platform.dart';
|
import 'package:platform/platform.dart';
|
||||||
|
|
||||||
|
@ -18,6 +21,7 @@ void main() {
|
||||||
MockHttpClientRequest mockHttpClientRequest;
|
MockHttpClientRequest mockHttpClientRequest;
|
||||||
MockHttpHeaders mockHttpHeaders;
|
MockHttpHeaders mockHttpHeaders;
|
||||||
BotDetector botDetector;
|
BotDetector botDetector;
|
||||||
|
PersistentToolState persistentToolState;
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
fakePlatform = FakePlatform()..environment = <String, String>{};
|
fakePlatform = FakePlatform()..environment = <String, String>{};
|
||||||
|
@ -25,9 +29,14 @@ void main() {
|
||||||
mockHttpClient = MockHttpClient();
|
mockHttpClient = MockHttpClient();
|
||||||
mockHttpClientRequest = MockHttpClientRequest();
|
mockHttpClientRequest = MockHttpClientRequest();
|
||||||
mockHttpHeaders = MockHttpHeaders();
|
mockHttpHeaders = MockHttpHeaders();
|
||||||
|
persistentToolState = PersistentToolState.test(
|
||||||
|
directory: MemoryFileSystem.test().currentDirectory,
|
||||||
|
logger: BufferLogger.test(),
|
||||||
|
);
|
||||||
botDetector = BotDetector(
|
botDetector = BotDetector(
|
||||||
platform: fakePlatform,
|
platform: fakePlatform,
|
||||||
httpClientFactory: () => mockHttpClient,
|
httpClientFactory: () => mockHttpClient,
|
||||||
|
persistentToolState: persistentToolState,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -35,13 +44,17 @@ void main() {
|
||||||
testWithoutContext('returns false unconditionally if BOT=false is set', () async {
|
testWithoutContext('returns false unconditionally if BOT=false is set', () async {
|
||||||
fakePlatform.environment['BOT'] = 'false';
|
fakePlatform.environment['BOT'] = 'false';
|
||||||
fakePlatform.environment['TRAVIS'] = 'true';
|
fakePlatform.environment['TRAVIS'] = 'true';
|
||||||
|
|
||||||
expect(await botDetector.isRunningOnBot, isFalse);
|
expect(await botDetector.isRunningOnBot, isFalse);
|
||||||
|
expect(persistentToolState.isRunningOnBot, isFalse);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWithoutContext('returns false unconditionally if FLUTTER_HOST is set', () async {
|
testWithoutContext('returns false unconditionally if FLUTTER_HOST is set', () async {
|
||||||
fakePlatform.environment['FLUTTER_HOST'] = 'foo';
|
fakePlatform.environment['FLUTTER_HOST'] = 'foo';
|
||||||
fakePlatform.environment['TRAVIS'] = 'true';
|
fakePlatform.environment['TRAVIS'] = 'true';
|
||||||
|
|
||||||
expect(await botDetector.isRunningOnBot, isFalse);
|
expect(await botDetector.isRunningOnBot, isFalse);
|
||||||
|
expect(persistentToolState.isRunningOnBot, isFalse);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWithoutContext('returns false with and without a terminal attached', () async {
|
testWithoutContext('returns false with and without a terminal attached', () async {
|
||||||
|
@ -52,12 +65,14 @@ void main() {
|
||||||
expect(await botDetector.isRunningOnBot, isFalse);
|
expect(await botDetector.isRunningOnBot, isFalse);
|
||||||
mockStdio.stdout.hasTerminal = false;
|
mockStdio.stdout.hasTerminal = false;
|
||||||
expect(await botDetector.isRunningOnBot, isFalse);
|
expect(await botDetector.isRunningOnBot, isFalse);
|
||||||
|
expect(persistentToolState.isRunningOnBot, isFalse);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWithoutContext('can test analytics outputs on bots when outputting to a file', () async {
|
testWithoutContext('can test analytics outputs on bots when outputting to a file', () async {
|
||||||
fakePlatform.environment['TRAVIS'] = 'true';
|
fakePlatform.environment['TRAVIS'] = 'true';
|
||||||
fakePlatform.environment['FLUTTER_ANALYTICS_LOG_FILE'] = '/some/file';
|
fakePlatform.environment['FLUTTER_ANALYTICS_LOG_FILE'] = '/some/file';
|
||||||
expect(await botDetector.isRunningOnBot, isFalse);
|
expect(await botDetector.isRunningOnBot, isFalse);
|
||||||
|
expect(persistentToolState.isRunningOnBot, isFalse);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWithoutContext('returns true when azure metadata is reachable', () async {
|
testWithoutContext('returns true when azure metadata is reachable', () async {
|
||||||
|
@ -65,12 +80,31 @@ void main() {
|
||||||
return Future<HttpClientRequest>.value(mockHttpClientRequest);
|
return Future<HttpClientRequest>.value(mockHttpClientRequest);
|
||||||
});
|
});
|
||||||
when(mockHttpClientRequest.headers).thenReturn(mockHttpHeaders);
|
when(mockHttpClientRequest.headers).thenReturn(mockHttpHeaders);
|
||||||
|
|
||||||
expect(await botDetector.isRunningOnBot, isTrue);
|
expect(await botDetector.isRunningOnBot, isTrue);
|
||||||
|
expect(persistentToolState.isRunningOnBot, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('caches azure bot detection results across instances', () async {
|
||||||
|
when(mockHttpClient.getUrl(any)).thenAnswer((_) {
|
||||||
|
return Future<HttpClientRequest>.value(mockHttpClientRequest);
|
||||||
|
});
|
||||||
|
when(mockHttpClientRequest.headers).thenReturn(mockHttpHeaders);
|
||||||
|
|
||||||
|
expect(await botDetector.isRunningOnBot, isTrue);
|
||||||
|
expect(await BotDetector(
|
||||||
|
platform: fakePlatform,
|
||||||
|
httpClientFactory: () => mockHttpClient,
|
||||||
|
persistentToolState: persistentToolState,
|
||||||
|
).isRunningOnBot, isTrue);
|
||||||
|
verify(mockHttpClient.getUrl(any)).called(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWithoutContext('returns true when running on borg', () async {
|
testWithoutContext('returns true when running on borg', () async {
|
||||||
fakePlatform.environment['BORG_ALLOC_DIR'] = 'true';
|
fakePlatform.environment['BORG_ALLOC_DIR'] = 'true';
|
||||||
|
|
||||||
expect(await botDetector.isRunningOnBot, isTrue);
|
expect(await botDetector.isRunningOnBot, isTrue);
|
||||||
|
expect(persistentToolState.isRunningOnBot, isTrue);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -94,6 +128,7 @@ void main() {
|
||||||
when(mockHttpClient.getUrl(any)).thenAnswer((_) {
|
when(mockHttpClient.getUrl(any)).thenAnswer((_) {
|
||||||
throw const SocketException('HTTP connection timed out');
|
throw const SocketException('HTTP connection timed out');
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(await azureDetector.isRunningOnAzure, isFalse);
|
expect(await azureDetector.isRunningOnAzure, isFalse);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -102,6 +137,7 @@ void main() {
|
||||||
return Future<HttpClientRequest>.value(mockHttpClientRequest);
|
return Future<HttpClientRequest>.value(mockHttpClientRequest);
|
||||||
});
|
});
|
||||||
when(mockHttpClientRequest.headers).thenReturn(mockHttpHeaders);
|
when(mockHttpClientRequest.headers).thenReturn(mockHttpHeaders);
|
||||||
|
|
||||||
expect(await azureDetector.isRunningOnAzure, isTrue);
|
expect(await azureDetector.isRunningOnAzure, isTrue);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue