Unverified Commit eb35f892 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Reland: Make the 'time to update' message depend up on the channel. (#24173) (#24321)

Fixes #24158
Re-lands #24173
parent 87156df6
...@@ -207,20 +207,34 @@ class FlutterVersion { ...@@ -207,20 +207,34 @@ class FlutterVersion {
/// The amount of time we wait before pinging the server to check for the /// The amount of time we wait before pinging the server to check for the
/// availability of a newer version of Flutter. /// availability of a newer version of Flutter.
@visibleForTesting @visibleForTesting
static const Duration kCheckAgeConsideredUpToDate = Duration(days: 3); static const Duration checkAgeConsideredUpToDate = Duration(days: 3);
/// We warn the user if the age of their Flutter installation is greater than /// We warn the user if the age of their Flutter installation is greater than
/// this duration. /// 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.
/// ///
/// This is set to 5 weeks because releases are currently around every 4 weeks. /// For example, for the beta channel, this is set to five weeks because
/// beta releases happen approximately every month.
@visibleForTesting @visibleForTesting
static const Duration kVersionAgeConsideredUpToDate = Duration(days: 35); 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. /// The amount of time we wait between issuing a warning.
/// ///
/// This is to avoid annoying users who are unable to upgrade right away. /// This is to avoid annoying users who are unable to upgrade right away.
@visibleForTesting @visibleForTesting
static const Duration kMaxTimeSinceLastWarning = Duration(days: 1); static const Duration maxTimeSinceLastWarning = Duration(days: 1);
/// The amount of time we pause for to let the user read the message about /// The amount of time we pause for to let the user read the message about
/// outdated Flutter installation. /// outdated Flutter installation.
...@@ -238,7 +252,7 @@ class FlutterVersion { ...@@ -238,7 +252,7 @@ class FlutterVersion {
static Future<void> resetFlutterVersionFreshnessCheck() async { static Future<void> resetFlutterVersionFreshnessCheck() async {
try { try {
await Cache.instance.getStampFileFor( await Cache.instance.getStampFileFor(
VersionCheckStamp.kFlutterVersionCheckStampFile, VersionCheckStamp.flutterVersionCheckStampFile,
).delete(); ).delete();
} on FileSystemException { } on FileSystemException {
// Ignore, since we don't mind if the file didn't exist in the first place. // Ignore, since we don't mind if the file didn't exist in the first place.
...@@ -258,7 +272,7 @@ class FlutterVersion { ...@@ -258,7 +272,7 @@ class FlutterVersion {
final DateTime localFrameworkCommitDate = DateTime.parse(frameworkCommitDate); final DateTime localFrameworkCommitDate = DateTime.parse(frameworkCommitDate);
final Duration frameworkAge = _clock.now().difference(localFrameworkCommitDate); final Duration frameworkAge = _clock.now().difference(localFrameworkCommitDate);
final bool installationSeemsOutdated = frameworkAge > kVersionAgeConsideredUpToDate; final bool installationSeemsOutdated = frameworkAge > versionAgeConsideredUpToDate(channel);
// Get whether there's a newer version on the remote. This only goes // 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 // to the server if we haven't checked recently so won't happen on every
...@@ -273,8 +287,8 @@ class FlutterVersion { ...@@ -273,8 +287,8 @@ class FlutterVersion {
// Do not load the stamp before the above server check as it may modify the stamp file. // Do not load the stamp before the above server check as it may modify the stamp file.
final VersionCheckStamp stamp = await VersionCheckStamp.load(); final VersionCheckStamp stamp = await VersionCheckStamp.load();
final DateTime lastTimeWarningWasPrinted = stamp.lastTimeWarningWasPrinted ?? _clock.ago(kMaxTimeSinceLastWarning * 2); final DateTime lastTimeWarningWasPrinted = stamp.lastTimeWarningWasPrinted ?? _clock.ago(maxTimeSinceLastWarning * 2);
final bool beenAWhileSinceWarningWasPrinted = _clock.now().difference(lastTimeWarningWasPrinted) > kMaxTimeSinceLastWarning; 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 // 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. // version is outdated.
...@@ -327,7 +341,7 @@ class FlutterVersion { ...@@ -327,7 +341,7 @@ class FlutterVersion {
/// Gets the release date of the latest available Flutter version. /// Gets the release date of the latest available Flutter version.
/// ///
/// This method sends a server request if it's been more than /// This method sends a server request if it's been more than
/// [kCheckAgeConsideredUpToDate] since the last version check. /// [checkAgeConsideredUpToDate] since the last version check.
/// ///
/// Returns null if the cached version is out-of-date or missing, and we are /// Returns null if the cached version is out-of-date or missing, and we are
/// unable to reach the server to get the latest version. /// unable to reach the server to get the latest version.
...@@ -339,7 +353,7 @@ class FlutterVersion { ...@@ -339,7 +353,7 @@ class FlutterVersion {
final Duration timeSinceLastCheck = _clock.now().difference(versionCheckStamp.lastTimeVersionWasChecked); final Duration timeSinceLastCheck = _clock.now().difference(versionCheckStamp.lastTimeVersionWasChecked);
// Don't ping the server too often. Return cached value if it's fresh. // Don't ping the server too often. Return cached value if it's fresh.
if (timeSinceLastCheck < kCheckAgeConsideredUpToDate) if (timeSinceLastCheck < checkAgeConsideredUpToDate)
return versionCheckStamp.lastKnownRemoteVersion; return versionCheckStamp.lastKnownRemoteVersion;
} }
...@@ -381,10 +395,10 @@ class VersionCheckStamp { ...@@ -381,10 +395,10 @@ class VersionCheckStamp {
/// The prefix of the stamp file where we cache Flutter version check data. /// The prefix of the stamp file where we cache Flutter version check data.
@visibleForTesting @visibleForTesting
static const String kFlutterVersionCheckStampFile = 'flutter_version_check'; static const String flutterVersionCheckStampFile = 'flutter_version_check';
static Future<VersionCheckStamp> load() async { static Future<VersionCheckStamp> load() async {
final String versionCheckStamp = Cache.instance.getStampFor(kFlutterVersionCheckStampFile); final String versionCheckStamp = Cache.instance.getStampFor(flutterVersionCheckStampFile);
if (versionCheckStamp != null) { if (versionCheckStamp != null) {
// Attempt to parse stamp JSON. // Attempt to parse stamp JSON.
...@@ -435,8 +449,8 @@ class VersionCheckStamp { ...@@ -435,8 +449,8 @@ class VersionCheckStamp {
if (newTimeWarningWasPrinted != null) if (newTimeWarningWasPrinted != null)
jsonData['lastTimeWarningWasPrinted'] = '$newTimeWarningWasPrinted'; jsonData['lastTimeWarningWasPrinted'] = '$newTimeWarningWasPrinted';
const JsonEncoder kPrettyJsonEncoder = JsonEncoder.withIndent(' '); const JsonEncoder prettyJsonEncoder = JsonEncoder.withIndent(' ');
Cache.instance.setStampFor(kFlutterVersionCheckStampFile, kPrettyJsonEncoder.convert(jsonData)); Cache.instance.setStampFor(flutterVersionCheckStampFile, prettyJsonEncoder.convert(jsonData));
} }
Map<String, String> toJson({ Map<String, String> toJson({
......
...@@ -188,7 +188,7 @@ void main() { ...@@ -188,7 +188,7 @@ void main() {
)).thenAnswer((_) => Future<Process>.value(createMockProcess())); )).thenAnswer((_) => Future<Process>.value(createMockProcess()));
final File versionCheckFile = Cache.instance.getStampFileFor( final File versionCheckFile = Cache.instance.getStampFileFor(
VersionCheckStamp.kFlutterVersionCheckStampFile, VersionCheckStamp.flutterVersionCheckStampFile,
); );
/// Create a bogus "leftover" version check file to make sure it gets /// Create a bogus "leftover" version check file to make sure it gets
......
...@@ -19,10 +19,8 @@ import 'src/common.dart'; ...@@ -19,10 +19,8 @@ import 'src/common.dart';
import 'src/context.dart'; import 'src/context.dart';
final SystemClock _testClock = SystemClock.fixed(DateTime(2015, 1, 1)); final SystemClock _testClock = SystemClock.fixed(DateTime(2015, 1, 1));
final DateTime _upToDateVersion = _testClock.ago(FlutterVersion.kVersionAgeConsideredUpToDate ~/ 2); final DateTime _stampUpToDate = _testClock.ago(FlutterVersion.checkAgeConsideredUpToDate ~/ 2);
final DateTime _outOfDateVersion = _testClock.ago(FlutterVersion.kVersionAgeConsideredUpToDate * 2); final DateTime _stampOutOfDate = _testClock.ago(FlutterVersion.checkAgeConsideredUpToDate * 2);
final DateTime _stampUpToDate = _testClock.ago(FlutterVersion.kCheckAgeConsideredUpToDate ~/ 2);
final DateTime _stampOutOfDate = _testClock.ago(FlutterVersion.kCheckAgeConsideredUpToDate * 2);
void main() { void main() {
MockProcessManager mockProcessManager; MockProcessManager mockProcessManager;
...@@ -33,324 +31,363 @@ void main() { ...@@ -33,324 +31,363 @@ void main() {
mockCache = MockCache(); mockCache = MockCache();
}); });
group('$FlutterVersion', () { for (String channel in FlutterVersion.officialChannels) {
setUpAll(() { DateTime getChannelUpToDateVersion() {
Cache.disableLocking(); return _testClock.ago(FlutterVersion.versionAgeConsideredUpToDate(channel) ~/ 2);
FlutterVersion.timeToPauseToLetUserReadTheMessage = Duration.zero; }
});
testUsingContext('prints nothing when Flutter installation looks fresh', () async {
fakeData(
mockProcessManager,
mockCache,
localCommitDate: _upToDateVersion,
// Server will be pinged because we haven't pinged within last x days
expectServerPing: true,
remoteCommitDate: _outOfDateVersion,
expectSetStamp: true);
await FlutterVersion.instance.checkFlutterVersionFreshness();
_expectVersionMessage('');
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(_testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
});
testUsingContext('prints nothing when Flutter installation looks out-of-date by is actually up-to-date', () async { DateTime getChannelOutOfDateVersion() {
fakeData( return _testClock.ago(FlutterVersion.versionAgeConsideredUpToDate(channel) * 2);
mockProcessManager, }
mockCache,
localCommitDate: _outOfDateVersion,
stamp: VersionCheckStamp(
lastTimeVersionWasChecked: _stampOutOfDate,
lastKnownRemoteVersion: _outOfDateVersion,
),
remoteCommitDate: _outOfDateVersion,
expectSetStamp: true,
expectServerPing: true,
);
final FlutterVersion version = FlutterVersion.instance;
await version.checkFlutterVersionFreshness();
_expectVersionMessage('');
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(_testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
});
testUsingContext('does not ping server when version stamp is up-to-date', () async { group('$FlutterVersion for $channel', () {
fakeData( setUpAll(() {
mockProcessManager, Cache.disableLocking();
mockCache, FlutterVersion.timeToPauseToLetUserReadTheMessage = Duration.zero;
localCommitDate: _outOfDateVersion, });
stamp: VersionCheckStamp(
lastTimeVersionWasChecked: _stampUpToDate,
lastKnownRemoteVersion: _upToDateVersion,
),
expectSetStamp: true,
);
final FlutterVersion version = FlutterVersion.instance;
await version.checkFlutterVersionFreshness();
_expectVersionMessage(FlutterVersion.newVersionAvailableMessage());
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(_testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
});
testUsingContext('does not print warning if printed recently', () async { testUsingContext('prints nothing when Flutter installation looks fresh', () async {
fakeData( fakeData(
mockProcessManager, mockProcessManager,
mockCache, mockCache,
localCommitDate: _outOfDateVersion, localCommitDate: getChannelUpToDateVersion(),
stamp: VersionCheckStamp( // Server will be pinged because we haven't pinged within last x days
expectServerPing: true,
remoteCommitDate: getChannelOutOfDateVersion(),
expectSetStamp: true,
channel: channel,
);
await FlutterVersion.instance.checkFlutterVersionFreshness();
_expectVersionMessage('');
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(_testClock),
ProcessManager: () => mockProcessManager,
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(
lastTimeVersionWasChecked: _stampOutOfDate,
lastKnownRemoteVersion: getChannelOutOfDateVersion(),
),
remoteCommitDate: getChannelOutOfDateVersion(),
expectSetStamp: true,
expectServerPing: true,
channel: channel,
);
final FlutterVersion version = FlutterVersion.instance;
await version.checkFlutterVersionFreshness();
_expectVersionMessage('');
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(_testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
});
testUsingContext('does not ping server when version stamp is up-to-date', () async {
fakeData(
mockProcessManager,
mockCache,
localCommitDate: getChannelOutOfDateVersion(),
stamp: VersionCheckStamp(
lastTimeVersionWasChecked: _stampUpToDate, lastTimeVersionWasChecked: _stampUpToDate,
lastKnownRemoteVersion: _upToDateVersion, lastKnownRemoteVersion: getChannelUpToDateVersion(),
), ),
expectSetStamp: true, expectSetStamp: true,
); channel: channel,
);
final FlutterVersion version = FlutterVersion.instance;
await version.checkFlutterVersionFreshness(); final FlutterVersion version = FlutterVersion.instance;
_expectVersionMessage(FlutterVersion.newVersionAvailableMessage()); await version.checkFlutterVersionFreshness();
expect((await VersionCheckStamp.load()).lastTimeWarningWasPrinted, _testClock.now()); _expectVersionMessage(FlutterVersion.newVersionAvailableMessage());
}, overrides: <Type, Generator>{
await version.checkFlutterVersionFreshness(); FlutterVersion: () => FlutterVersion(_testClock),
_expectVersionMessage(''); ProcessManager: () => mockProcessManager,
}, overrides: <Type, Generator>{ Cache: () => mockCache,
FlutterVersion: () => FlutterVersion(_testClock), });
ProcessManager: () => mockProcessManager,
Cache: () => mockCache, testUsingContext('does not print warning if printed recently', () async {
}); fakeData(
mockProcessManager,
mockCache,
localCommitDate: getChannelOutOfDateVersion(),
stamp: VersionCheckStamp(
lastTimeVersionWasChecked: _stampUpToDate,
lastKnownRemoteVersion: getChannelUpToDateVersion(),
),
expectSetStamp: true,
channel: channel,
);
final FlutterVersion version = FlutterVersion.instance;
await version.checkFlutterVersionFreshness();
_expectVersionMessage(FlutterVersion.newVersionAvailableMessage());
expect((await VersionCheckStamp.load()).lastTimeWarningWasPrinted, _testClock.now());
await version.checkFlutterVersionFreshness();
_expectVersionMessage('');
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(_testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
});
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,
);
final FlutterVersion version = FlutterVersion.instance;
testUsingContext('pings server when version stamp is missing then does not', () async { await version.checkFlutterVersionFreshness();
fakeData( _expectVersionMessage(FlutterVersion.newVersionAvailableMessage());
mockProcessManager,
mockCache,
localCommitDate: _outOfDateVersion,
remoteCommitDate: _upToDateVersion,
expectSetStamp: true,
expectServerPing: true,
);
final FlutterVersion version = FlutterVersion.instance;
await version.checkFlutterVersionFreshness();
_expectVersionMessage(FlutterVersion.newVersionAvailableMessage());
// Immediate subsequent check is not expected to ping the server.
fakeData(
mockProcessManager,
mockCache,
localCommitDate: _outOfDateVersion,
stamp: await VersionCheckStamp.load(),
);
await version.checkFlutterVersionFreshness();
_expectVersionMessage('');
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(_testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
});
testUsingContext('pings server when version stamp is out-of-date', () async { // Immediate subsequent check is not expected to ping the server.
fakeData( fakeData(
mockProcessManager, mockProcessManager,
mockCache, mockCache,
localCommitDate: _outOfDateVersion, localCommitDate: getChannelOutOfDateVersion(),
stamp: VersionCheckStamp( stamp: await VersionCheckStamp.load(),
channel: channel,
);
await version.checkFlutterVersionFreshness();
_expectVersionMessage('');
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(_testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
});
testUsingContext('pings server when version stamp is out-of-date', () async {
fakeData(
mockProcessManager,
mockCache,
localCommitDate: getChannelOutOfDateVersion(),
stamp: VersionCheckStamp(
lastTimeVersionWasChecked: _stampOutOfDate, lastTimeVersionWasChecked: _stampOutOfDate,
lastKnownRemoteVersion: _testClock.ago(const Duration(days: 2)), lastKnownRemoteVersion: _testClock.ago(const Duration(days: 2)),
), ),
remoteCommitDate: _upToDateVersion, remoteCommitDate: getChannelUpToDateVersion(),
expectSetStamp: true, expectSetStamp: true,
expectServerPing: true, expectServerPing: true,
); channel: channel,
final FlutterVersion version = FlutterVersion.instance; );
final FlutterVersion version = FlutterVersion.instance;
await version.checkFlutterVersionFreshness();
_expectVersionMessage(FlutterVersion.newVersionAvailableMessage()); await version.checkFlutterVersionFreshness();
}, overrides: <Type, Generator>{ _expectVersionMessage(FlutterVersion.newVersionAvailableMessage());
FlutterVersion: () => FlutterVersion(_testClock), }, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager, FlutterVersion: () => FlutterVersion(_testClock),
Cache: () => mockCache, ProcessManager: () => mockProcessManager,
}); Cache: () => mockCache,
});
testUsingContext('does not print warning when unable to connect to server if not out of date', () async {
fakeData( testUsingContext('does not print warning when unable to connect to server if not out of date', () async {
mockProcessManager, fakeData(
mockCache,
localCommitDate: _upToDateVersion,
errorOnFetch: true,
expectServerPing: true,
expectSetStamp: true,
);
final FlutterVersion version = FlutterVersion.instance;
await version.checkFlutterVersionFreshness();
_expectVersionMessage('');
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(_testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
});
testUsingContext('prints warning when unable to connect to server if really out of date', () async {
fakeData(
mockProcessManager,
mockCache,
localCommitDate: _outOfDateVersion,
errorOnFetch: true,
expectServerPing: true,
expectSetStamp: true
);
final FlutterVersion version = FlutterVersion.instance;
await version.checkFlutterVersionFreshness();
_expectVersionMessage(FlutterVersion.versionOutOfDateMessage(_testClock.now().difference(_outOfDateVersion)));
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(_testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
});
testUsingContext('versions comparison', () async {
fakeData(
mockProcessManager, mockProcessManager,
mockCache, mockCache,
localCommitDate: _outOfDateVersion, localCommitDate: getChannelUpToDateVersion(),
errorOnFetch: true, errorOnFetch: true,
expectServerPing: true, expectServerPing: true,
expectSetStamp: true expectSetStamp: true,
); channel: channel,
final FlutterVersion version = FlutterVersion.instance; );
final FlutterVersion version = FlutterVersion.instance;
when(mockProcessManager.runSync(
<String>['git', 'merge-base', '--is-ancestor', 'abcdef', '123456'], await version.checkFlutterVersionFreshness();
workingDirectory: anyNamed('workingDirectory'), _expectVersionMessage('');
)).thenReturn(ProcessResult(1, 0, '', '')); }, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(_testClock),
expect( ProcessManager: () => mockProcessManager,
version.checkRevisionAncestry( Cache: () => mockCache,
tentativeDescendantRevision: '123456', });
tentativeAncestorRevision: 'abcdef',
), testUsingContext('prints warning when unable to connect to server if really out of date', () async {
true fakeData(
); mockProcessManager,
mockCache,
verify(mockProcessManager.runSync( localCommitDate: getChannelOutOfDateVersion(),
<String>['git', 'merge-base', '--is-ancestor', 'abcdef', '123456'], errorOnFetch: true,
workingDirectory: anyNamed('workingDirectory'), expectServerPing: true,
)); expectSetStamp: true,
}, channel: channel,
overrides: <Type, Generator>{ );
FlutterVersion: () => FlutterVersion(_testClock), final FlutterVersion version = FlutterVersion.instance;
ProcessManager: () => mockProcessManager,
}); await version.checkFlutterVersionFreshness();
}); _expectVersionMessage(FlutterVersion.versionOutOfDateMessage(_testClock.now().difference(getChannelOutOfDateVersion())));
}, overrides: <Type, Generator>{
group('$VersionCheckStamp', () { FlutterVersion: () => FlutterVersion(_testClock),
void _expectDefault(VersionCheckStamp stamp) { ProcessManager: () => mockProcessManager,
expect(stamp.lastKnownRemoteVersion, isNull); Cache: () => mockCache,
expect(stamp.lastTimeVersionWasChecked, isNull); });
expect(stamp.lastTimeWarningWasPrinted, isNull);
} testUsingContext('versions comparison', () async {
fakeData(
testUsingContext('loads blank when stamp file missing', () async { mockProcessManager,
fakeData(mockProcessManager, mockCache); mockCache,
_expectDefault(await VersionCheckStamp.load()); localCommitDate: getChannelOutOfDateVersion(),
}, overrides: <Type, Generator>{ errorOnFetch: true,
FlutterVersion: () => FlutterVersion(_testClock), expectServerPing: true,
ProcessManager: () => mockProcessManager, expectSetStamp: true,
Cache: () => mockCache, channel: channel,
}); );
final FlutterVersion version = FlutterVersion.instance;
testUsingContext('loads blank when stamp file is malformed JSON', () async {
fakeData(mockProcessManager, mockCache, stampJson: '<'); when(mockProcessManager.runSync(
_expectDefault(await VersionCheckStamp.load()); <String>['git', 'merge-base', '--is-ancestor', 'abcdef', '123456'],
}, overrides: <Type, Generator>{ workingDirectory: anyNamed('workingDirectory'),
FlutterVersion: () => FlutterVersion(_testClock), )).thenReturn(ProcessResult(1, 0, '', ''));
ProcessManager: () => mockProcessManager,
Cache: () => mockCache, expect(
version.checkRevisionAncestry(
tentativeDescendantRevision: '123456',
tentativeAncestorRevision: 'abcdef',
),
true);
verify(mockProcessManager.runSync(
<String>['git', 'merge-base', '--is-ancestor', 'abcdef', '123456'],
workingDirectory: anyNamed('workingDirectory'),
));
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(_testClock),
ProcessManager: () => mockProcessManager,
});
}); });
testUsingContext('loads blank when stamp file is well-formed but invalid JSON', () async { group('$VersionCheckStamp for $channel', () {
fakeData(mockProcessManager, mockCache, stampJson: '[]'); void _expectDefault(VersionCheckStamp stamp) {
_expectDefault(await VersionCheckStamp.load()); expect(stamp.lastKnownRemoteVersion, isNull);
}, overrides: <Type, Generator>{ expect(stamp.lastTimeVersionWasChecked, isNull);
FlutterVersion: () => FlutterVersion(_testClock), expect(stamp.lastTimeWarningWasPrinted, isNull);
ProcessManager: () => mockProcessManager, }
Cache: () => mockCache,
});
testUsingContext('loads valid JSON', () async { testUsingContext('loads blank when stamp file missing', () async {
fakeData(mockProcessManager, mockCache, stampJson: ''' fakeData(mockProcessManager, mockCache, channel: channel);
_expectDefault(await VersionCheckStamp.load());
}, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(_testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
});
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(_testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
});
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(_testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
});
testUsingContext('loads valid JSON', () async {
fakeData(
mockProcessManager,
mockCache,
stampJson: '''
{ {
"lastKnownRemoteVersion": "${_testClock.ago(const Duration(days: 1))}", "lastKnownRemoteVersion": "${_testClock.ago(const Duration(days: 1))}",
"lastTimeVersionWasChecked": "${_testClock.ago(const Duration(days: 2))}", "lastTimeVersionWasChecked": "${_testClock.ago(const Duration(days: 2))}",
"lastTimeWarningWasPrinted": "${_testClock.now()}" "lastTimeWarningWasPrinted": "${_testClock.now()}"
} }
'''); ''',
channel: channel,
final VersionCheckStamp stamp = await VersionCheckStamp.load(); );
expect(stamp.lastKnownRemoteVersion, _testClock.ago(const Duration(days: 1)));
expect(stamp.lastTimeVersionWasChecked, _testClock.ago(const Duration(days: 2))); final VersionCheckStamp stamp = await VersionCheckStamp.load();
expect(stamp.lastTimeWarningWasPrinted, _testClock.now()); expect(stamp.lastKnownRemoteVersion, _testClock.ago(const Duration(days: 1)));
}, overrides: <Type, Generator>{ expect(stamp.lastTimeVersionWasChecked, _testClock.ago(const Duration(days: 2)));
FlutterVersion: () => FlutterVersion(_testClock), expect(stamp.lastTimeWarningWasPrinted, _testClock.now());
ProcessManager: () => mockProcessManager, }, overrides: <Type, Generator>{
Cache: () => mockCache, FlutterVersion: () => FlutterVersion(_testClock),
}); ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
testUsingContext('stores version stamp', () async { });
fakeData(mockProcessManager, mockCache, expectSetStamp: true);
testUsingContext('stores version stamp', () async {
_expectDefault(await VersionCheckStamp.load()); fakeData(
mockProcessManager,
final VersionCheckStamp stamp = VersionCheckStamp( mockCache,
lastKnownRemoteVersion: _testClock.ago(const Duration(days: 1)), expectSetStamp: true,
lastTimeVersionWasChecked: _testClock.ago(const Duration(days: 2)), channel: channel,
lastTimeWarningWasPrinted: _testClock.now(), );
);
await stamp.store(); _expectDefault(await VersionCheckStamp.load());
final VersionCheckStamp storedStamp = await VersionCheckStamp.load(); final VersionCheckStamp stamp = VersionCheckStamp(
expect(storedStamp.lastKnownRemoteVersion, _testClock.ago(const Duration(days: 1))); lastKnownRemoteVersion: _testClock.ago(const Duration(days: 1)),
expect(storedStamp.lastTimeVersionWasChecked, _testClock.ago(const Duration(days: 2))); lastTimeVersionWasChecked: _testClock.ago(const Duration(days: 2)),
expect(storedStamp.lastTimeWarningWasPrinted, _testClock.now()); lastTimeWarningWasPrinted: _testClock.now(),
}, overrides: <Type, Generator>{ );
FlutterVersion: () => FlutterVersion(_testClock), await stamp.store();
ProcessManager: () => mockProcessManager,
Cache: () => mockCache, final VersionCheckStamp storedStamp = await VersionCheckStamp.load();
}); expect(storedStamp.lastKnownRemoteVersion, _testClock.ago(const Duration(days: 1)));
expect(storedStamp.lastTimeVersionWasChecked, _testClock.ago(const Duration(days: 2)));
testUsingContext('overwrites individual fields', () async { expect(storedStamp.lastTimeWarningWasPrinted, _testClock.now());
fakeData(mockProcessManager, mockCache, expectSetStamp: true); }, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(_testClock),
_expectDefault(await VersionCheckStamp.load()); ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
final VersionCheckStamp stamp = VersionCheckStamp( });
lastKnownRemoteVersion: _testClock.ago(const Duration(days: 10)),
lastTimeVersionWasChecked: _testClock.ago(const Duration(days: 9)), testUsingContext('overwrites individual fields', () async {
lastTimeWarningWasPrinted: _testClock.ago(const Duration(days: 8)), fakeData(
); mockProcessManager,
await stamp.store( mockCache,
newKnownRemoteVersion: _testClock.ago(const Duration(days: 1)), expectSetStamp: true,
newTimeVersionWasChecked: _testClock.ago(const Duration(days: 2)), channel: channel,
newTimeWarningWasPrinted: _testClock.now(), );
);
_expectDefault(await VersionCheckStamp.load());
final VersionCheckStamp storedStamp = await VersionCheckStamp.load();
expect(storedStamp.lastKnownRemoteVersion, _testClock.ago(const Duration(days: 1))); final VersionCheckStamp stamp = VersionCheckStamp(
expect(storedStamp.lastTimeVersionWasChecked, _testClock.ago(const Duration(days: 2))); lastKnownRemoteVersion: _testClock.ago(const Duration(days: 10)),
expect(storedStamp.lastTimeWarningWasPrinted, _testClock.now()); lastTimeVersionWasChecked: _testClock.ago(const Duration(days: 9)),
}, overrides: <Type, Generator>{ lastTimeWarningWasPrinted: _testClock.ago(const Duration(days: 8)),
FlutterVersion: () => FlutterVersion(_testClock), );
ProcessManager: () => mockProcessManager, await stamp.store(
Cache: () => mockCache, 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(_testClock),
ProcessManager: () => mockProcessManager,
Cache: () => mockCache,
});
}); });
}); }
} }
void _expectVersionMessage(String message) { void _expectVersionMessage(String message) {
...@@ -369,6 +406,7 @@ void fakeData( ...@@ -369,6 +406,7 @@ void fakeData(
bool errorOnFetch = false, bool errorOnFetch = false,
bool expectSetStamp = false, bool expectSetStamp = false,
bool expectServerPing = false, bool expectServerPing = false,
String channel = 'master',
}) { }) {
ProcessResult success(String standardOutput) { ProcessResult success(String standardOutput) {
return ProcessResult(1, 0, standardOutput, ''); return ProcessResult(1, 0, standardOutput, '');
...@@ -379,19 +417,21 @@ void fakeData( ...@@ -379,19 +417,21 @@ void fakeData(
} }
when(cache.getStampFor(any)).thenAnswer((Invocation invocation) { when(cache.getStampFor(any)).thenAnswer((Invocation invocation) {
expect(invocation.positionalArguments.single, VersionCheckStamp.kFlutterVersionCheckStampFile); expect(invocation.positionalArguments.single, VersionCheckStamp.flutterVersionCheckStampFile);
if (stampJson != null) if (stampJson != null) {
return stampJson; return stampJson;
}
if (stamp != null) if (stamp != null) {
return json.encode(stamp.toJson()); return json.encode(stamp.toJson());
}
return null; return null;
}); });
when(cache.setStampFor(any, any)).thenAnswer((Invocation invocation) { when(cache.setStampFor(any, any)).thenAnswer((Invocation invocation) {
expect(invocation.positionalArguments.first, VersionCheckStamp.kFlutterVersionCheckStampFile); expect(invocation.positionalArguments.first, VersionCheckStamp.flutterVersionCheckStampFile);
if (expectSetStamp) { if (expectSetStamp) {
stamp = VersionCheckStamp.fromJson(json.decode(invocation.positionalArguments[1])); stamp = VersionCheckStamp.fromJson(json.decode(invocation.positionalArguments[1]));
...@@ -405,10 +445,7 @@ void fakeData( ...@@ -405,10 +445,7 @@ void fakeData(
bool argsAre(String a1, [String a2, String a3, String a4, String a5, String a6, String a7, String a8]) { bool argsAre(String a1, [String a2, String a3, String a4, String a5, String a6, String a7, String a8]) {
const ListEquality<String> equality = ListEquality<String>(); const ListEquality<String> equality = ListEquality<String>();
final List<String> args = invocation.positionalArguments.single; final List<String> args = invocation.positionalArguments.single;
final List<String> expectedArgs = final List<String> expectedArgs = <String>[a1, a2, a3, a4, a5, a6, a7, a8].where((String arg) => arg != null).toList();
<String>[a1, a2, a3, a4, a5, a6, a7, a8]
.where((String arg) => arg != null)
.toList();
return equality.equals(args, expectedArgs); return equality.equals(args, expectedArgs);
} }
...@@ -418,11 +455,12 @@ void fakeData( ...@@ -418,11 +455,12 @@ void fakeData(
return success(''); return success('');
} else if (argsAre('git', 'remote', 'add', '__flutter_version_check__', 'https://github.com/flutter/flutter.git')) { } else if (argsAre('git', 'remote', 'add', '__flutter_version_check__', 'https://github.com/flutter/flutter.git')) {
return success(''); return success('');
} else if (argsAre('git', 'fetch', '__flutter_version_check__', 'master')) { } else if (argsAre('git', 'fetch', '__flutter_version_check__', channel)) {
if (!expectServerPing) if (!expectServerPing) {
fail('Did not expect server ping'); fail('Did not expect server ping');
}
return errorOnFetch ? failure(128) : success(''); return errorOnFetch ? failure(128) : success('');
} else if (remoteCommitDate != null && argsAre('git', 'log', '__flutter_version_check__/master', '-n', '1', '--pretty=format:%ad', '--date=iso')) { } else if (remoteCommitDate != null && argsAre('git', 'log', '__flutter_version_check__/$channel', '-n', '1', '--pretty=format:%ad', '--date=iso')) {
return success(remoteCommitDate.toString()); return success(remoteCommitDate.toString());
} }
...@@ -438,7 +476,7 @@ void fakeData( ...@@ -438,7 +476,7 @@ void fakeData(
<String>['git', 'rev-parse', '--abbrev-ref', '--symbolic', '@{u}'], <String>['git', 'rev-parse', '--abbrev-ref', '--symbolic', '@{u}'],
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'), environment: anyNamed('environment'),
)).thenReturn(ProcessResult(101, 0, 'master', '')); )).thenReturn(ProcessResult(101, 0, channel, ''));
when(pm.runSync( when(pm.runSync(
<String>['git', 'rev-parse', '--abbrev-ref', 'HEAD'], <String>['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
...@@ -462,4 +500,5 @@ void fakeData( ...@@ -462,4 +500,5 @@ void fakeData(
} }
class MockProcessManager extends Mock implements ProcessManager {} class MockProcessManager extends Mock implements ProcessManager {}
class MockCache extends Mock implements Cache {} 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