Unverified Commit 6a75e486 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] remove substantial mocking from version test (#84007)

parent 588b9a3a
......@@ -7,6 +7,7 @@ import 'package:meta/meta.dart';
import 'base/common.dart';
import 'base/file_system.dart';
import 'base/io.dart';
import 'base/logger.dart';
import 'base/process.dart';
import 'base/time.dart';
import 'cache.dart';
......@@ -221,6 +222,38 @@ class FlutterVersion {
}
}
/// Checks if the currently installed version of Flutter is up-to-date, and
/// warns the user if it isn't.
///
/// This function must run while [Cache.lock] is acquired because it reads and
/// writes shared cache files.
Future<void> checkFlutterVersionFreshness() async {
// Don't perform update checks if we're not on an official channel.
if (!kOfficialChannels.contains(channel)) {
return;
}
DateTime localFrameworkCommitDate;
try {
localFrameworkCommitDate = DateTime.parse(_latestGitCommitDate(
lenient: false
));
} on VersionCheckError {
// Don't perform the update check if the version check failed.
return;
}
final DateTime? latestFlutterCommitDate = await _getLatestAvailableFlutterDate();
await checkVersionFreshness(
this,
clock: _clock,
localFrameworkCommitDate: localFrameworkCommitDate,
latestFlutterCommitDate: latestFlutterCommitDate,
logger: globals.logger,
cache: globals.cache,
pauseTime: timeToPauseToLetUserReadTheMessage,
);
}
/// The name of the temporary git remote used to check for the latest
/// available Flutter framework version.
///
......@@ -296,45 +329,6 @@ class FlutterVersion {
return _branch!;
}
/// The amount of time we wait before pinging the server to check for the
/// availability of a newer version of Flutter.
@visibleForTesting
static const Duration checkAgeConsideredUpToDate = Duration(days: 3);
/// We warn the user if the age of their Flutter installation is greater than
/// this duration. The durations are slightly longer than the expected release
/// cadence for each channel, to give the user a grace period before they get
/// notified.
///
/// For example, for the beta channel, this is set to five weeks because
/// beta releases happen approximately every month.
@visibleForTesting
static Duration versionAgeConsideredUpToDate(String channel) {
switch (channel) {
case 'stable':
return const Duration(days: 365 ~/ 2); // Six months
case 'beta':
return const Duration(days: 7 * 8); // Eight weeks
case 'dev':
return const Duration(days: 7 * 4); // Four weeks
default:
return const Duration(days: 7 * 3); // Three weeks
}
}
/// The amount of time we wait between issuing a warning.
///
/// This is to avoid annoying users who are unable to upgrade right away.
@visibleForTesting
static const Duration maxTimeSinceLastWarning = Duration(days: 1);
/// The amount of time we pause for to let the user read the message about
/// outdated Flutter installation.
///
/// This can be customized in tests to speed them up.
@visibleForTesting
static Duration timeToPauseToLetUserReadTheMessage = const Duration(seconds: 2);
/// Reset the version freshness information by removing the stamp file.
///
/// New version freshness information will be regenerated when
......@@ -351,67 +345,6 @@ class FlutterVersion {
}
}
/// Checks if the currently installed version of Flutter is up-to-date, and
/// warns the user if it isn't.
///
/// This function must run while [Cache.lock] is acquired because it reads and
/// writes shared cache files.
Future<void> checkFlutterVersionFreshness() async {
// Don't perform update checks if we're not on an official channel.
if (!kOfficialChannels.contains(channel)) {
return;
}
DateTime localFrameworkCommitDate;
try {
localFrameworkCommitDate = DateTime.parse(_latestGitCommitDate(
lenient: false
));
} on VersionCheckError {
// Don't perform the update check if the version check failed.
return;
}
final Duration frameworkAge = _clock.now().difference(localFrameworkCommitDate);
final bool installationSeemsOutdated = frameworkAge > versionAgeConsideredUpToDate(channel);
// Get whether there's a newer version on the remote. This only goes
// to the server if we haven't checked recently so won't happen on every
// command.
final DateTime? latestFlutterCommitDate = await _getLatestAvailableFlutterDate();
final VersionCheckResult remoteVersionStatus = latestFlutterCommitDate == null
? VersionCheckResult.unknown
: latestFlutterCommitDate.isAfter(localFrameworkCommitDate)
? VersionCheckResult.newVersionAvailable
: VersionCheckResult.versionIsCurrent;
// Do not load the stamp before the above server check as it may modify the stamp file.
final VersionCheckStamp stamp = await VersionCheckStamp.load();
final DateTime lastTimeWarningWasPrinted = stamp.lastTimeWarningWasPrinted ?? _clock.ago(maxTimeSinceLastWarning * 2);
final bool beenAWhileSinceWarningWasPrinted = _clock.now().difference(lastTimeWarningWasPrinted) > maxTimeSinceLastWarning;
// We show a warning if either we know there is a new remote version, or we couldn't tell but the local
// version is outdated.
final bool canShowWarning =
remoteVersionStatus == VersionCheckResult.newVersionAvailable ||
(remoteVersionStatus == VersionCheckResult.unknown &&
installationSeemsOutdated);
if (beenAWhileSinceWarningWasPrinted && canShowWarning) {
final String updateMessage =
remoteVersionStatus == VersionCheckResult.newVersionAvailable
? newVersionAvailableMessage()
: versionOutOfDateMessage(frameworkAge);
globals.printStatus(updateMessage, emphasis: true);
await Future.wait<void>(<Future<void>>[
stamp.store(
newTimeWarningWasPrinted: _clock.now(),
),
Future<void>.delayed(timeToPauseToLetUserReadTheMessage),
]);
}
}
/// log.showSignature=false is a user setting and it will break things,
/// so we want to disable it for every git log call. This is a convenience
/// wrapper that does that.
......@@ -420,32 +353,6 @@ class FlutterVersion {
return <String>['git', '-c', 'log.showSignature=false', 'log'] + args;
}
@visibleForTesting
static String versionOutOfDateMessage(Duration frameworkAge) {
String warning = 'WARNING: your installation of Flutter is ${frameworkAge.inDays} days old.';
// Append enough spaces to match the message box width.
warning += ' ' * (74 - warning.length);
return '''
╔════════════════════════════════════════════════════════════════════════════╗
$warning
║ ║
║ To update to the latest version, run "flutter upgrade". ║
╚════════════════════════════════════════════════════════════════════════════╝
''';
}
@visibleForTesting
static String newVersionAvailableMessage() {
return '''
╔════════════════════════════════════════════════════════════════════════════╗
║ A new version of Flutter is available! ║
║ ║
║ To update to the latest version, run "flutter upgrade". ║
╚════════════════════════════════════════════════════════════════════════════╝
''';
}
/// Gets the release date of the latest available Flutter version.
///
/// This method sends a server request if it's been more than
......@@ -455,7 +362,7 @@ class FlutterVersion {
/// unable to reach the server to get the latest version.
Future<DateTime?> _getLatestAvailableFlutterDate() async {
globals.cache.checkLockAcquired();
final VersionCheckStamp versionCheckStamp = await VersionCheckStamp.load();
final VersionCheckStamp versionCheckStamp = await VersionCheckStamp.load(globals.cache, globals.logger);
if (versionCheckStamp.lastTimeVersionWasChecked != null) {
final Duration timeSinceLastCheck = _clock.now().difference(
......@@ -510,8 +417,8 @@ class VersionCheckStamp {
@visibleForTesting
static const String flutterVersionCheckStampFile = 'flutter_version_check';
static Future<VersionCheckStamp> load() async {
final String? versionCheckStamp = globals.cache.getStampFor(flutterVersionCheckStampFile);
static Future<VersionCheckStamp> load(Cache cache, Logger logger) async {
final String? versionCheckStamp = cache.getStampFor(flutterVersionCheckStampFile);
if (versionCheckStamp != null) {
// Attempt to parse stamp JSON.
......@@ -520,11 +427,11 @@ class VersionCheckStamp {
if (jsonObject is Map<String, dynamic>) {
return fromJson(jsonObject);
} else {
globals.printTrace('Warning: expected version stamp to be a Map but found: $jsonObject');
logger.printTrace('Warning: expected version stamp to be a Map but found: $jsonObject');
}
} on Exception catch (error, stackTrace) {
// Do not crash if JSON is malformed.
globals.printTrace('${error.runtimeType}: $error\n$stackTrace');
logger.printTrace('${error.runtimeType}: $error\n$stackTrace');
}
}
......@@ -550,6 +457,7 @@ class VersionCheckStamp {
DateTime? newTimeVersionWasChecked,
DateTime? newKnownRemoteVersion,
DateTime? newTimeWarningWasPrinted,
Cache? cache,
}) async {
final Map<String, String> jsonData = toJson();
......@@ -566,7 +474,7 @@ class VersionCheckStamp {
}
const JsonEncoder prettyJsonEncoder = JsonEncoder.withIndent(' ');
globals.cache.setStampFor(flutterVersionCheckStampFile, prettyJsonEncoder.convert(jsonData));
(cache ?? globals.cache).setStampFor(flutterVersionCheckStampFile, prettyJsonEncoder.convert(jsonData));
}
Map<String, String> toJson({
......@@ -837,3 +745,122 @@ enum VersionCheckResult {
/// A newer version is available.
newVersionAvailable,
}
@visibleForTesting
Future<void> checkVersionFreshness(FlutterVersion version, {
required DateTime localFrameworkCommitDate,
required DateTime? latestFlutterCommitDate,
required SystemClock clock,
required Cache cache,
required Logger logger,
Duration pauseTime = Duration.zero,
}) async {
// Don't perform update checks if we're not on an official channel.
if (!kOfficialChannels.contains(version.channel)) {
return;
}
final Duration frameworkAge = clock.now().difference(localFrameworkCommitDate);
final bool installationSeemsOutdated = frameworkAge > versionAgeConsideredUpToDate(version.channel);
// Get whether there's a newer version on the remote. This only goes
// to the server if we haven't checked recently so won't happen on every
// command.
final VersionCheckResult remoteVersionStatus = latestFlutterCommitDate == null
? VersionCheckResult.unknown
: latestFlutterCommitDate.isAfter(localFrameworkCommitDate)
? VersionCheckResult.newVersionAvailable
: VersionCheckResult.versionIsCurrent;
// Do not load the stamp before the above server check as it may modify the stamp file.
final VersionCheckStamp stamp = await VersionCheckStamp.load(cache, logger);
final DateTime lastTimeWarningWasPrinted = stamp.lastTimeWarningWasPrinted ?? clock.ago(maxTimeSinceLastWarning * 2);
final bool beenAWhileSinceWarningWasPrinted = clock.now().difference(lastTimeWarningWasPrinted) > maxTimeSinceLastWarning;
// We show a warning if either we know there is a new remote version, or we couldn't tell but the local
// version is outdated.
final bool canShowWarning =
remoteVersionStatus == VersionCheckResult.newVersionAvailable ||
(remoteVersionStatus == VersionCheckResult.unknown &&
installationSeemsOutdated);
if (beenAWhileSinceWarningWasPrinted && canShowWarning) {
final String updateMessage =
remoteVersionStatus == VersionCheckResult.newVersionAvailable
? newVersionAvailableMessage()
: versionOutOfDateMessage(frameworkAge);
logger.printStatus(updateMessage, emphasis: true);
await Future.wait<void>(<Future<void>>[
stamp.store(
newTimeWarningWasPrinted: clock.now(),
cache: cache,
),
Future<void>.delayed(pauseTime),
]);
}
}
/// The amount of time we wait before pinging the server to check for the
/// availability of a newer version of Flutter.
@visibleForTesting
const Duration checkAgeConsideredUpToDate = Duration(days: 3);
/// We warn the user if the age of their Flutter installation is greater than
/// this duration. The durations are slightly longer than the expected release
/// cadence for each channel, to give the user a grace period before they get
/// notified.
///
/// For example, for the beta channel, this is set to five weeks because
/// beta releases happen approximately every month.
@visibleForTesting
Duration versionAgeConsideredUpToDate(String channel) {
switch (channel) {
case 'stable':
return const Duration(days: 365 ~/ 2); // Six months
case 'beta':
return const Duration(days: 7 * 8); // Eight weeks
case 'dev':
return const Duration(days: 7 * 4); // Four weeks
default:
return const Duration(days: 7 * 3); // Three weeks
}
}
/// The amount of time we wait between issuing a warning.
///
/// This is to avoid annoying users who are unable to upgrade right away.
@visibleForTesting
const Duration maxTimeSinceLastWarning = Duration(days: 1);
/// The amount of time we pause for to let the user read the message about
/// outdated Flutter installation.
///
/// This can be customized in tests to speed them up.
@visibleForTesting
Duration timeToPauseToLetUserReadTheMessage = const Duration(seconds: 2);
@visibleForTesting
String versionOutOfDateMessage(Duration frameworkAge) {
String warning = 'WARNING: your installation of Flutter is ${frameworkAge.inDays} days old.';
// Append enough spaces to match the message box width.
warning += ' ' * (74 - warning.length);
return '''
╔════════════════════════════════════════════════════════════════════════════╗
$warning
║ ║
║ To update to the latest version, run "flutter upgrade". ║
╚════════════════════════════════════════════════════════════════════════════╝
''';
}
@visibleForTesting
String newVersionAvailableMessage() {
return '''
╔════════════════════════════════════════════════════════════════════════════╗
║ A new version of Flutter is available! ║
║ ║
║ To update to the latest version, run "flutter upgrade". ║
╚════════════════════════════════════════════════════════════════════════════╝
''';
}
......@@ -6,13 +6,10 @@
import 'dart:convert';
import 'package:collection/collection.dart' show ListEquality;
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/process.dart';
import 'package:flutter_tools/src/base/time.dart';
import 'package:flutter_tools/src/base/utils.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/globals_null_migrated.dart' as globals;
import 'package:flutter_tools/src/version.dart';
......@@ -23,17 +20,15 @@ import '../src/context.dart';
import '../src/fake_process_manager.dart';
final SystemClock _testClock = SystemClock.fixed(DateTime(2015, 1, 1));
final DateTime _stampUpToDate = _testClock.ago(FlutterVersion.checkAgeConsideredUpToDate ~/ 2);
final DateTime _stampOutOfDate = _testClock.ago(FlutterVersion.checkAgeConsideredUpToDate * 2);
final DateTime _stampUpToDate = _testClock.ago(checkAgeConsideredUpToDate ~/ 2);
final DateTime _stampOutOfDate = _testClock.ago(checkAgeConsideredUpToDate * 2);
void main() {
MockProcessManager mockProcessManager;
MockCache mockCache;
FakeProcessManager processManager;
setUp(() {
processManager = FakeProcessManager.empty();
mockProcessManager = MockProcessManager();
mockCache = MockCache();
});
......@@ -47,17 +42,17 @@ void main() {
for (final String channel in kOfficialChannels) {
DateTime getChannelUpToDateVersion() {
return _testClock.ago(FlutterVersion.versionAgeConsideredUpToDate(channel) ~/ 2);
return _testClock.ago(versionAgeConsideredUpToDate(channel) ~/ 2);
}
DateTime getChannelOutOfDateVersion() {
return _testClock.ago(FlutterVersion.versionAgeConsideredUpToDate(channel) * 2);
return _testClock.ago(versionAgeConsideredUpToDate(channel) * 2);
}
group('$FlutterVersion for $channel', () {
setUpAll(() {
Cache.disableLocking();
FlutterVersion.timeToPauseToLetUserReadTheMessage = Duration.zero;
timeToPauseToLetUserReadTheMessage = Duration.zero;
});
testUsingContext('prints nothing when Flutter installation looks fresh', () async {
......@@ -139,7 +134,7 @@ void main() {
expect(flutterVersion.getVersionString(redactUnknownBranches: true), '$channel/1234abcd');
expect(flutterVersion.getBranchName(redactUnknownBranches: true), channel);
_expectVersionMessage('');
_expectVersionMessage('', testLogger);
expect(processManager.hasRemainingExpectations, isFalse);
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(clock: _testClock),
......@@ -147,172 +142,159 @@ void main() {
Cache: () => mockCache,
});
testUsingContext('prints nothing when Flutter installation looks out-of-date but is actually up-to-date', () async {
fakeData(
mockProcessManager,
mockCache,
localCommitDate: getChannelOutOfDateVersion(),
stamp: VersionCheckStamp(
testWithoutContext('prints nothing when Flutter installation looks out-of-date but is actually up-to-date', () async {
final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel);
final BufferLogger logger = BufferLogger.test();
final VersionCheckStamp stamp = VersionCheckStamp(
lastTimeVersionWasChecked: _stampOutOfDate,
lastKnownRemoteVersion: getChannelOutOfDateVersion(),
),
remoteCommitDate: getChannelOutOfDateVersion(),
expectSetStamp: true,
expectServerPing: true,
channel: channel,
);
final FlutterVersion version = globals.flutterVersion;
await version.checkFlutterVersionFreshness();
_expectVersionMessage('');
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(clock: _testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
when(mockCache.getStampFor(VersionCheckStamp.flutterVersionCheckStampFile))
.thenReturn(json.encode(stamp));
await checkVersionFreshness(
flutterVersion,
cache: mockCache,
clock: _testClock,
logger: logger,
localFrameworkCommitDate: getChannelOutOfDateVersion(),
latestFlutterCommitDate: getChannelOutOfDateVersion(),
);
_expectVersionMessage('', logger);
});
testUsingContext('does not ping server when version stamp is up-to-date', () async {
fakeData(
mockProcessManager,
mockCache,
localCommitDate: getChannelOutOfDateVersion(),
stamp: VersionCheckStamp(
testWithoutContext('does not ping server when version stamp is up-to-date', () async {
final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel);
final BufferLogger logger = BufferLogger.test();
final VersionCheckStamp stamp = VersionCheckStamp(
lastTimeVersionWasChecked: _stampUpToDate,
lastKnownRemoteVersion: getChannelUpToDateVersion(),
),
expectSetStamp: true,
channel: channel,
);
final FlutterVersion version = globals.flutterVersion;
await version.checkFlutterVersionFreshness();
_expectVersionMessage(FlutterVersion.newVersionAvailableMessage());
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(clock: _testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
when(mockCache.getStampFor(VersionCheckStamp.flutterVersionCheckStampFile))
.thenReturn(json.encode(stamp));
await checkVersionFreshness(
flutterVersion,
cache: mockCache,
clock: _testClock,
logger: logger,
localFrameworkCommitDate: getChannelOutOfDateVersion(),
latestFlutterCommitDate: getChannelUpToDateVersion(),
);
_expectVersionMessage(newVersionAvailableMessage(), logger);
verify(mockCache.setStampFor(VersionCheckStamp.flutterVersionCheckStampFile, any)).called(1);
});
testUsingContext('does not print warning if printed recently', () async {
fakeData(
mockProcessManager,
mockCache,
localCommitDate: getChannelOutOfDateVersion(),
stamp: VersionCheckStamp(
testWithoutContext('does not print warning if printed recently', () async {
final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel);
final BufferLogger logger = BufferLogger.test();
final VersionCheckStamp stamp = VersionCheckStamp(
lastTimeVersionWasChecked: _stampUpToDate,
lastKnownRemoteVersion: getChannelUpToDateVersion(),
),
expectSetStamp: true,
channel: channel,
lastTimeWarningWasPrinted: _testClock.now(),
);
final FlutterVersion version = globals.flutterVersion;
await version.checkFlutterVersionFreshness();
_expectVersionMessage(FlutterVersion.newVersionAvailableMessage());
expect((await VersionCheckStamp.load()).lastTimeWarningWasPrinted, _testClock.now());
when(mockCache.getStampFor(VersionCheckStamp.flutterVersionCheckStampFile))
.thenReturn(json.encode(stamp));
await version.checkFlutterVersionFreshness();
_expectVersionMessage('');
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(clock: _testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
await checkVersionFreshness(
flutterVersion,
cache: mockCache,
clock: _testClock,
logger: logger,
localFrameworkCommitDate: getChannelOutOfDateVersion(),
latestFlutterCommitDate: getChannelUpToDateVersion(),
);
_expectVersionMessage('', logger);
});
testUsingContext('pings server when version stamp is missing then does not', () async {
fakeData(
mockProcessManager,
mockCache,
localCommitDate: getChannelOutOfDateVersion(),
remoteCommitDate: getChannelUpToDateVersion(),
expectSetStamp: true,
expectServerPing: true,
channel: channel,
testWithoutContext('pings server when version stamp is missing', () async {
final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel);
final BufferLogger logger = BufferLogger.test();
when(mockCache.getStampFor(VersionCheckStamp.flutterVersionCheckStampFile))
.thenReturn('{}');
await checkVersionFreshness(
flutterVersion,
cache: mockCache,
clock: _testClock,
logger: logger,
localFrameworkCommitDate: getChannelOutOfDateVersion(),
latestFlutterCommitDate: getChannelUpToDateVersion(),
);
final FlutterVersion version = globals.flutterVersion;
await version.checkFlutterVersionFreshness();
_expectVersionMessage(FlutterVersion.newVersionAvailableMessage());
// Immediate subsequent check is not expected to ping the server.
fakeData(
mockProcessManager,
mockCache,
localCommitDate: getChannelOutOfDateVersion(),
stamp: await VersionCheckStamp.load(),
channel: channel,
);
await version.checkFlutterVersionFreshness();
_expectVersionMessage('');
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(clock: _testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
_expectVersionMessage(newVersionAvailableMessage(), logger);
verify(mockCache.setStampFor(VersionCheckStamp.flutterVersionCheckStampFile, any)).called(1);
});
testUsingContext('pings server when version stamp is out-of-date', () async {
fakeData(
mockProcessManager,
mockCache,
localCommitDate: getChannelOutOfDateVersion(),
stamp: VersionCheckStamp(
testWithoutContext('pings server when version stamp is out-of-date', () async {
final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel);
final BufferLogger logger = BufferLogger.test();
final VersionCheckStamp stamp = VersionCheckStamp(
lastTimeVersionWasChecked: _stampOutOfDate,
lastKnownRemoteVersion: _testClock.ago(const Duration(days: 2)),
),
remoteCommitDate: getChannelUpToDateVersion(),
expectSetStamp: true,
expectServerPing: true,
channel: channel,
);
final FlutterVersion version = globals.flutterVersion;
await version.checkFlutterVersionFreshness();
_expectVersionMessage(FlutterVersion.newVersionAvailableMessage());
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(clock: _testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
});
when(mockCache.getStampFor(VersionCheckStamp.flutterVersionCheckStampFile))
.thenReturn(json.encode(stamp));
testUsingContext('does not print warning when unable to connect to server if not out of date', () async {
fakeData(
mockProcessManager,
mockCache,
localCommitDate: getChannelUpToDateVersion(),
errorOnFetch: true,
expectServerPing: true,
expectSetStamp: true,
channel: channel,
await checkVersionFreshness(
flutterVersion,
cache: mockCache,
clock: _testClock,
logger: logger,
localFrameworkCommitDate: getChannelOutOfDateVersion(),
latestFlutterCommitDate: getChannelUpToDateVersion(),
);
final FlutterVersion version = globals.flutterVersion;
await version.checkFlutterVersionFreshness();
_expectVersionMessage('');
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(clock: _testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
_expectVersionMessage(newVersionAvailableMessage(), logger);
});
testUsingContext('prints warning when unable to connect to server if really out of date', () async {
fakeData(
mockProcessManager,
mockCache,
localCommitDate: getChannelOutOfDateVersion(),
errorOnFetch: true,
expectServerPing: true,
expectSetStamp: true,
channel: channel,
testWithoutContext('does not print warning when unable to connect to server if not out of date', () async {
final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel);
final BufferLogger logger = BufferLogger.test();
when(mockCache.getStampFor(VersionCheckStamp.flutterVersionCheckStampFile))
.thenReturn('{}');
await checkVersionFreshness(
flutterVersion,
cache: mockCache,
clock: _testClock,
logger: logger,
localFrameworkCommitDate: getChannelUpToDateVersion(),
latestFlutterCommitDate: null, // Failed to get remote version
);
final FlutterVersion version = globals.flutterVersion;
await version.checkFlutterVersionFreshness();
_expectVersionMessage(FlutterVersion.versionOutOfDateMessage(_testClock.now().difference(getChannelOutOfDateVersion())));
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(clock: _testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
_expectVersionMessage('', logger);
});
testWithoutContext('prints warning when unable to connect to server if really out of date', () async {
final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel);
final BufferLogger logger = BufferLogger.test();
final VersionCheckStamp stamp = VersionCheckStamp(
lastTimeVersionWasChecked: _stampOutOfDate,
lastKnownRemoteVersion: _testClock.ago(const Duration(days: 2)),
);
when(mockCache.getStampFor(VersionCheckStamp.flutterVersionCheckStampFile))
.thenReturn(json.encode(stamp));
await checkVersionFreshness(
flutterVersion,
cache: mockCache,
clock: _testClock,
logger: logger,
localFrameworkCommitDate: getChannelOutOfDateVersion(),
latestFlutterCommitDate: null, // Failed to get remote version
);
_expectVersionMessage(versionOutOfDateMessage(_testClock.now().difference(getChannelOutOfDateVersion())), logger);
});
group('$VersionCheckStamp for $channel', () {
......@@ -322,118 +304,44 @@ void main() {
expect(stamp.lastTimeWarningWasPrinted, isNull);
}
testUsingContext('loads blank when stamp file missing', () async {
fakeData(mockProcessManager, mockCache, channel: channel);
_expectDefault(await VersionCheckStamp.load());
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(clock: _testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
testWithoutContext('loads blank when stamp file missing', () async {
when(mockCache.getStampFor(VersionCheckStamp.flutterVersionCheckStampFile))
.thenReturn(null);
_expectDefault(await VersionCheckStamp.load(mockCache, BufferLogger.test()));
});
testUsingContext('loads blank when stamp file is malformed JSON', () async {
fakeData(mockProcessManager, mockCache, stampJson: '<', channel: channel);
_expectDefault(await VersionCheckStamp.load());
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(clock: _testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
testWithoutContext('loads blank when stamp file is malformed JSON', () async {
when(mockCache.getStampFor(VersionCheckStamp.flutterVersionCheckStampFile))
.thenReturn('<');
_expectDefault(await VersionCheckStamp.load(mockCache, BufferLogger.test()));
});
testUsingContext('loads blank when stamp file is well-formed but invalid JSON', () async {
fakeData(
mockProcessManager,
mockCache,
stampJson: '[]',
channel: channel,
);
_expectDefault(await VersionCheckStamp.load());
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(clock: _testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
testWithoutContext('loads blank when stamp file is well-formed but invalid JSON', () async {
when(mockCache.getStampFor(VersionCheckStamp.flutterVersionCheckStampFile))
.thenReturn('[]');
_expectDefault(await VersionCheckStamp.load(mockCache, BufferLogger.test()));
});
testUsingContext('loads valid JSON', () async {
fakeData(
mockProcessManager,
mockCache,
stampJson: '''
testWithoutContext('loads valid JSON', () async {
final String value = '''
{
"lastKnownRemoteVersion": "${_testClock.ago(const Duration(days: 1))}",
"lastTimeVersionWasChecked": "${_testClock.ago(const Duration(days: 2))}",
"lastTimeWarningWasPrinted": "${_testClock.now()}"
}
''',
channel: channel,
);
''';
when(mockCache.getStampFor(VersionCheckStamp.flutterVersionCheckStampFile))
.thenReturn(value);
final VersionCheckStamp stamp = await VersionCheckStamp.load(mockCache, BufferLogger.test());
final VersionCheckStamp stamp = await VersionCheckStamp.load();
expect(stamp.lastKnownRemoteVersion, _testClock.ago(const Duration(days: 1)));
expect(stamp.lastTimeVersionWasChecked, _testClock.ago(const Duration(days: 2)));
expect(stamp.lastTimeWarningWasPrinted, _testClock.now());
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(clock: _testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
});
testUsingContext('stores version stamp', () async {
fakeData(
mockProcessManager,
mockCache,
expectSetStamp: true,
channel: channel,
);
_expectDefault(await VersionCheckStamp.load());
final VersionCheckStamp stamp = VersionCheckStamp(
lastKnownRemoteVersion: _testClock.ago(const Duration(days: 1)),
lastTimeVersionWasChecked: _testClock.ago(const Duration(days: 2)),
lastTimeWarningWasPrinted: _testClock.now(),
);
await stamp.store();
final VersionCheckStamp storedStamp = await VersionCheckStamp.load();
expect(storedStamp.lastKnownRemoteVersion, _testClock.ago(const Duration(days: 1)));
expect(storedStamp.lastTimeVersionWasChecked, _testClock.ago(const Duration(days: 2)));
expect(storedStamp.lastTimeWarningWasPrinted, _testClock.now());
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(clock: _testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
});
testUsingContext('overwrites individual fields', () async {
fakeData(
mockProcessManager,
mockCache,
expectSetStamp: true,
channel: channel,
);
_expectDefault(await VersionCheckStamp.load());
final VersionCheckStamp stamp = VersionCheckStamp(
lastKnownRemoteVersion: _testClock.ago(const Duration(days: 10)),
lastTimeVersionWasChecked: _testClock.ago(const Duration(days: 9)),
lastTimeWarningWasPrinted: _testClock.ago(const Duration(days: 8)),
);
await stamp.store(
newKnownRemoteVersion: _testClock.ago(const Duration(days: 1)),
newTimeVersionWasChecked: _testClock.ago(const Duration(days: 2)),
newTimeWarningWasPrinted: _testClock.now(),
);
final VersionCheckStamp storedStamp = await VersionCheckStamp.load();
expect(storedStamp.lastKnownRemoteVersion, _testClock.ago(const Duration(days: 1)));
expect(storedStamp.lastTimeVersionWasChecked, _testClock.ago(const Duration(days: 2)));
expect(storedStamp.lastTimeWarningWasPrinted, _testClock.now());
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(clock: _testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
});
});
}
......@@ -702,130 +610,16 @@ void main() {
});
}
void _expectVersionMessage(String message) {
expect(testLogger.statusText.trim(), message.trim());
testLogger.clear();
void _expectVersionMessage(String message, BufferLogger logger) {
expect(logger.statusText.trim(), message.trim());
logger.clear();
}
void fakeData(
ProcessManager pm,
Cache cache, {
DateTime localCommitDate,
DateTime remoteCommitDate,
VersionCheckStamp stamp,
String stampJson,
bool errorOnFetch = false,
bool expectSetStamp = false,
bool expectServerPing = false,
String channel = 'master',
}) {
ProcessResult success(String standardOutput) {
return ProcessResult(1, 0, standardOutput, '');
}
ProcessResult failure(int exitCode) {
return ProcessResult(1, exitCode, '', 'error');
}
when(cache.getStampFor(any)).thenAnswer((Invocation invocation) {
expect(invocation.positionalArguments.single, VersionCheckStamp.flutterVersionCheckStampFile);
if (stampJson != null) {
return stampJson;
}
if (stamp != null) {
return json.encode(stamp.toJson());
}
return null;
});
when(cache.setStampFor(any, any)).thenAnswer((Invocation invocation) {
expect(invocation.positionalArguments.first, VersionCheckStamp.flutterVersionCheckStampFile);
if (expectSetStamp) {
stamp = VersionCheckStamp.fromJson(castStringKeyedMap(json.decode(invocation.positionalArguments[1] as String)));
return;
}
throw StateError('Unexpected call to Cache.setStampFor(${invocation.positionalArguments}, ${invocation.namedArguments})');
});
ProcessResult syncAnswer(Invocation invocation) {
bool argsAre(String a1, [ String a2, String a3, String a4, String a5, String a6, String a7, String a8, String a9 ]) {
const ListEquality<String> equality = ListEquality<String>();
final List<String> args = invocation.positionalArguments.single as List<String>;
final List<String> expectedArgs = <String>[a1, a2, a3, a4, a5, a6, a7, a8, a9].where((String arg) => arg != null).toList();
return equality.equals(args, expectedArgs);
}
bool listArgsAre(List<String> a) {
return Function.apply(argsAre, a) as bool;
}
if (listArgsAre(FlutterVersion.gitLog(<String>['-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__', channel)) {
if (!expectServerPing) {
fail('Did not expect server ping');
}
return errorOnFetch ? failure(128) : success('');
// Careful here! argsAre accepts 9 arguments and FlutterVersion.gitLog adds 4.
} else if (remoteCommitDate != null && listArgsAre(FlutterVersion.gitLog(<String>['__flutter_version_check__/$channel', '-n', '1', '--pretty=format:%ad', '--date=iso']))) {
return success(remoteCommitDate.toString());
} else if (argsAre('git', 'fetch', 'https://github.com/flutter/flutter.git', '--tags')) {
return success('');
}
class MockCache extends Mock implements Cache {}
throw StateError('Unexpected call to ProcessManager.run(${invocation.positionalArguments}, ${invocation.namedArguments})');
}
class FakeFlutterVersion extends Fake implements FlutterVersion {
FakeFlutterVersion(this.channel);
when(pm.runSync(any, workingDirectory: anyNamed('workingDirectory'))).thenAnswer(syncAnswer);
when(pm.run(any, workingDirectory: anyNamed('workingDirectory'))).thenAnswer((Invocation invocation) async {
return syncAnswer(invocation);
});
when(pm.runSync(
<String>['git', 'rev-parse', '--abbrev-ref', '--symbolic', '@{u}'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenReturn(ProcessResult(101, 0, channel, ''));
when(pm.runSync(
<String>['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenReturn(ProcessResult(102, 0, 'branch', ''));
when(pm.runSync(
FlutterVersion.gitLog(<String>['-n', '1', '--pretty=format:%H']),
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenReturn(ProcessResult(103, 0, '1234abcd', ''));
when(pm.runSync(
FlutterVersion.gitLog(<String>['-n', '1', '--pretty=format:%ar']),
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenReturn(ProcessResult(104, 0, '1 second ago', ''));
when(pm.runSync(
<String>['git', 'fetch', 'https://github.com/flutter/flutter', '--tags'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenReturn(ProcessResult(105, 0, '', ''));
when(pm.runSync(
<String>['git', 'tag', '--points-at', '1234abcd'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenReturn(ProcessResult(106, 0, '', ''));
when(pm.runSync(
<String>['git', 'describe', '--match', '*.*.*', '--long', '--tags', '1234abcd'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenReturn(ProcessResult(107, 0, 'v0.1.2-3-1234abcd', ''));
@override
final String channel;
}
class MockProcessManager extends Mock implements ProcessManager {}
class MockCache extends Mock implements Cache {}
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