warn about outdated Flutter installations (#9163)
diff --git a/packages/flutter_tools/test/src/version_test.dart b/packages/flutter_tools/test/src/version_test.dart
new file mode 100644
index 0000000..47da067
--- /dev/null
+++ b/packages/flutter_tools/test/src/version_test.dart
@@ -0,0 +1,223 @@
+// Copyright 2017 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 'dart:convert';
+
+import 'package:collection/collection.dart';
+import 'package:meta/meta.dart';
+import 'package:mockito/mockito.dart';
+import 'package:process/process.dart';
+import 'package:quiver/time.dart';
+import 'package:test/test.dart';
+
+import 'package:flutter_tools/src/base/context.dart';
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/version.dart';
+
+import 'context.dart';
+
+const JsonEncoder _kPrettyJsonEncoder = const JsonEncoder.withIndent(' ');
+final Clock _testClock = new Clock.fixed(new DateTime(2015, 1, 1));
+final DateTime _upToDateVersion = _testClock.agoBy(FlutterVersion.kVersionAgeConsideredUpToDate ~/ 2);
+final DateTime _outOfDateVersion = _testClock.agoBy(FlutterVersion.kVersionAgeConsideredUpToDate * 2);
+final DateTime _stampUpToDate = _testClock.agoBy(FlutterVersion.kCheckAgeConsideredUpToDate ~/ 2);
+final DateTime _stampOutOfDate = _testClock.agoBy(FlutterVersion.kCheckAgeConsideredUpToDate * 2);
+const String _stampMissing = '____stamp_missing____';
+
+void main() {
+ group('FlutterVersion', () {
+ setUpAll(() {
+ Cache.disableLocking();
+ });
+
+ testFlutterVersion('prints nothing when Flutter installation looks fresh', () async {
+ fakeData(localCommitDate: _upToDateVersion);
+ await FlutterVersion.instance.checkFlutterVersionFreshness();
+ _expectVersionMessage('');
+ });
+
+ testFlutterVersion('prints nothing when Flutter installation looks out-of-date by is actually up-to-date', () async {
+ final FlutterVersion version = FlutterVersion.instance;
+
+ fakeData(
+ localCommitDate: _outOfDateVersion,
+ versionCheckStamp: _testStamp(
+ lastTimeVersionWasChecked: _stampOutOfDate,
+ lastKnownRemoteVersion: _outOfDateVersion,
+ ),
+ remoteCommitDate: _outOfDateVersion,
+ expectSetStamp: true,
+ );
+
+ await version.checkFlutterVersionFreshness();
+ _expectVersionMessage('');
+ });
+
+ testFlutterVersion('does not ping server when version stamp is up-to-date', () async {
+ final FlutterVersion version = FlutterVersion.instance;
+
+ fakeData(
+ localCommitDate: _outOfDateVersion,
+ versionCheckStamp: _testStamp(
+ lastTimeVersionWasChecked: _stampUpToDate,
+ lastKnownRemoteVersion: _upToDateVersion,
+ ),
+ );
+
+ await version.checkFlutterVersionFreshness();
+ _expectVersionMessage(FlutterVersion.versionOutOfDateMessage(_testClock.now().difference(_outOfDateVersion)));
+ });
+
+ testFlutterVersion('pings server when version stamp is missing', () async {
+ final FlutterVersion version = FlutterVersion.instance;
+
+ fakeData(
+ localCommitDate: _outOfDateVersion,
+ versionCheckStamp: _stampMissing,
+ remoteCommitDate: _upToDateVersion,
+ expectSetStamp: true,
+ );
+
+ await version.checkFlutterVersionFreshness();
+ _expectVersionMessage(FlutterVersion.versionOutOfDateMessage(_testClock.now().difference(_outOfDateVersion)));
+ });
+
+ testFlutterVersion('pings server when version stamp is out-of-date', () async {
+ final FlutterVersion version = FlutterVersion.instance;
+
+ fakeData(
+ localCommitDate: _outOfDateVersion,
+ versionCheckStamp: _testStamp(
+ lastTimeVersionWasChecked: _stampOutOfDate,
+ lastKnownRemoteVersion: _testClock.ago(days: 2),
+ ),
+ remoteCommitDate: _upToDateVersion,
+ expectSetStamp: true,
+ );
+
+ await version.checkFlutterVersionFreshness();
+ _expectVersionMessage(FlutterVersion.versionOutOfDateMessage(_testClock.now().difference(_outOfDateVersion)));
+ });
+
+ testFlutterVersion('ignores network issues', () async {
+ final FlutterVersion version = FlutterVersion.instance;
+
+ fakeData(
+ localCommitDate: _outOfDateVersion,
+ versionCheckStamp: _stampMissing,
+ errorOnFetch: true,
+ );
+
+ await version.checkFlutterVersionFreshness();
+ _expectVersionMessage('');
+ });
+ });
+}
+
+void _expectVersionMessage(String message) {
+ final BufferLogger logger = context[Logger];
+ expect(logger.statusText.trim(), message.trim());
+}
+
+String _testStamp({@required DateTime lastTimeVersionWasChecked, @required DateTime lastKnownRemoteVersion}) {
+ return _kPrettyJsonEncoder.convert(<String, String>{
+ 'lastTimeVersionWasChecked': '$lastTimeVersionWasChecked',
+ 'lastKnownRemoteVersion': '$lastKnownRemoteVersion',
+ });
+}
+
+void testFlutterVersion(String description, dynamic testMethod()) {
+ testUsingContext(
+ description,
+ testMethod,
+ overrides: <Type, Generator>{
+ FlutterVersion: () => new FlutterVersion(_testClock),
+ ProcessManager: () => new MockProcessManager(),
+ Cache: () => new MockCache(),
+ },
+ );
+}
+
+void fakeData({
+ @required DateTime localCommitDate,
+ DateTime remoteCommitDate,
+ String versionCheckStamp,
+ bool expectSetStamp: false,
+ bool errorOnFetch: false,
+}) {
+ final MockProcessManager pm = context[ProcessManager];
+ final MockCache cache = context[Cache];
+
+ ProcessResult success(String standardOutput) {
+ return new ProcessResult(1, 0, standardOutput, '');
+ }
+
+ ProcessResult failure(int exitCode) {
+ return new ProcessResult(1, exitCode, '', 'error');
+ }
+
+ when(cache.getStampFor(any)).thenAnswer((Invocation invocation) {
+ expect(invocation.positionalArguments.single, FlutterVersion.kFlutterVersionCheckStampFile);
+
+ if (versionCheckStamp == _stampMissing) {
+ return null;
+ }
+
+ if (versionCheckStamp != null) {
+ return versionCheckStamp;
+ }
+
+ throw new StateError('Unexpected call to Cache.getStampFor(${invocation.positionalArguments}, ${invocation.namedArguments})');
+ });
+
+ when(cache.setStampFor(any, any)).thenAnswer((Invocation invocation) {
+ expect(invocation.positionalArguments.first, FlutterVersion.kFlutterVersionCheckStampFile);
+
+ if (expectSetStamp) {
+ expect(invocation.positionalArguments[1], _testStamp(
+ lastKnownRemoteVersion: remoteCommitDate,
+ lastTimeVersionWasChecked: _testClock.now(),
+ ));
+ return null;
+ }
+
+ throw new StateError('Unexpected call to Cache.setStampFor(${invocation.positionalArguments}, ${invocation.namedArguments})');
+ });
+
+ final Answering syncAnswer = (Invocation invocation) {
+ bool argsAre(String a1, [String a2, String a3, String a4, String a5, String a6, String a7, String a8]) {
+ const ListEquality<String> equality = const ListEquality<String>();
+ final List<String> args = invocation.positionalArguments.single;
+ final List<String> expectedArgs =
+ <String>[a1, a2, a3, a4, a5, a6, a7, a8]
+ .where((String arg) => arg != null)
+ .toList();
+ return equality.equals(args, expectedArgs);
+ }
+
+ if (argsAre('git', 'log', '-n', '1', '--pretty=format:%ad', '--date=iso')) {
+ return success(localCommitDate.toString());
+ } else if (argsAre('git', 'remote')) {
+ return success('');
+ } else if (argsAre('git', 'remote', 'add', '__flutter_version_check__', 'https://github.com/flutter/flutter.git')) {
+ return success('');
+ } else if (argsAre('git', 'fetch', '__flutter_version_check__', 'master')) {
+ return errorOnFetch ? failure(128) : success('');
+ } else if (remoteCommitDate != null && argsAre('git', 'log', '__flutter_version_check__/master', '-n', '1', '--pretty=format:%ad', '--date=iso')) {
+ return success(remoteCommitDate.toString());
+ }
+
+ throw new StateError('Unexpected call to ProcessManager.run(${invocation.positionalArguments}, ${invocation.namedArguments})');
+ };
+
+ when(pm.runSync(any, workingDirectory: any)).thenAnswer(syncAnswer);
+ when(pm.run(any, workingDirectory: any)).thenAnswer((Invocation invocation) async {
+ return syncAnswer(invocation);
+ });
+}
+
+class MockProcessManager extends Mock implements ProcessManager {}
+class MockCache extends Mock implements Cache {}