1595 lines
55 KiB
Dart
1595 lines
55 KiB
Dart
// Copyright 2014 The Flutter 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:file/file.dart';
|
|
import 'package:file/memory.dart';
|
|
import 'package:file_testing/file_testing.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/cache.dart';
|
|
import 'package:flutter_tools/src/features.dart';
|
|
import 'package:flutter_tools/src/git.dart';
|
|
import 'package:flutter_tools/src/globals.dart' as globals;
|
|
import 'package:flutter_tools/src/version.dart';
|
|
import 'package:meta/meta.dart';
|
|
import 'package:test/fake.dart';
|
|
|
|
import '../src/common.dart';
|
|
import '../src/context.dart';
|
|
import '../src/fake_process_manager.dart';
|
|
import '../src/fakes.dart' show FakeFlutterVersion, TestFeatureFlags;
|
|
|
|
final _testClock = SystemClock.fixed(DateTime.utc(2015));
|
|
final DateTime _stampUpToDate = _testClock.ago(
|
|
VersionFreshnessValidator.checkAgeConsideredUpToDate ~/ 2,
|
|
);
|
|
final DateTime _stampOutOfDate = _testClock.ago(
|
|
VersionFreshnessValidator.checkAgeConsideredUpToDate * 2,
|
|
);
|
|
|
|
void main() {
|
|
late FakeCache cache;
|
|
late FakeProcessManager processManager;
|
|
late Git git;
|
|
late BufferLogger testLogger;
|
|
|
|
setUp(() {
|
|
processManager = FakeProcessManager.empty();
|
|
cache = FakeCache();
|
|
testLogger = BufferLogger.test();
|
|
git = Git(
|
|
currentPlatform: FakePlatform(),
|
|
runProcessWith: ProcessUtils(processManager: processManager, logger: testLogger),
|
|
);
|
|
});
|
|
|
|
testUsingContext('Channel enum and string transform to each other', () {
|
|
for (final Channel channel in Channel.values) {
|
|
expect(getNameForChannel(channel), kOfficialChannels.toList()[channel.index]);
|
|
}
|
|
expect(
|
|
kOfficialChannels.toList().map((String str) => getChannelForName(str)).toList(),
|
|
Channel.values,
|
|
);
|
|
});
|
|
|
|
/// Mocks the series of commands used to determine the Flutter version for `master`.
|
|
@useResult
|
|
List<FakeCommand> mockGitTagHistory({
|
|
required String latestTag,
|
|
required String headRef,
|
|
required String ancestorRef,
|
|
required int commitsBetweenRefs,
|
|
}) {
|
|
return [
|
|
FakeCommand(
|
|
command: const [
|
|
'git',
|
|
'for-each-ref',
|
|
'--sort=-v:refname',
|
|
'--count=1',
|
|
'--format=%(refname:short)',
|
|
'refs/tags/[0-9]*.*.*',
|
|
],
|
|
stdout: latestTag,
|
|
),
|
|
FakeCommand(command: ['git', 'merge-base', headRef, latestTag], stdout: ancestorRef),
|
|
FakeCommand(
|
|
command: ['git', 'rev-list', '--count', '$ancestorRef..$headRef'],
|
|
stdout: '$commitsBetweenRefs',
|
|
),
|
|
];
|
|
}
|
|
|
|
for (final String channel in kOfficialChannels) {
|
|
DateTime getChannelUpToDateVersion() {
|
|
return _testClock.ago(VersionFreshnessValidator.versionAgeConsideredUpToDate(channel) ~/ 2);
|
|
}
|
|
|
|
DateTime getChannelOutOfDateVersion() {
|
|
return _testClock.ago(VersionFreshnessValidator.versionAgeConsideredUpToDate(channel) * 2);
|
|
}
|
|
|
|
group('$FlutterVersion for $channel', () {
|
|
late FileSystem fs;
|
|
const flutterRoot = '/path/to/flutter';
|
|
|
|
setUpAll(() {
|
|
Cache.disableLocking();
|
|
VersionFreshnessValidator.timeToPauseToLetUserReadTheMessage = Duration.zero;
|
|
});
|
|
|
|
setUp(() {
|
|
fs = MemoryFileSystem.test();
|
|
fs.directory(flutterRoot).createSync(recursive: true);
|
|
FlutterVersion.getVersionFile(fs, flutterRoot).createSync(recursive: true);
|
|
fs.file(fs.path.join(flutterRoot, 'version')).createSync(recursive: true);
|
|
});
|
|
|
|
testUsingContext(
|
|
'prints nothing when Flutter installation looks fresh $channel',
|
|
() async {
|
|
const flutterUpstreamUrl = 'https://github.com/flutter/flutter.git';
|
|
processManager.addCommands(<FakeCommand>[
|
|
const FakeCommand(
|
|
command: <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%H',
|
|
],
|
|
stdout: '1234abcd',
|
|
),
|
|
const FakeCommand(command: <String>['git', 'tag', '--points-at', '1234abcd']),
|
|
...mockGitTagHistory(
|
|
latestTag: '',
|
|
headRef: '1234abcd',
|
|
ancestorRef: '',
|
|
commitsBetweenRefs: 0,
|
|
),
|
|
FakeCommand(
|
|
command: const <String>['git', 'symbolic-ref', '--short', 'HEAD'],
|
|
stdout: channel,
|
|
),
|
|
FakeCommand(
|
|
command: const <String>[
|
|
'git',
|
|
'rev-parse',
|
|
'--abbrev-ref',
|
|
'--symbolic',
|
|
'@{upstream}',
|
|
],
|
|
stdout: 'origin/$channel',
|
|
),
|
|
const FakeCommand(
|
|
command: <String>['git', 'ls-remote', '--get-url', 'origin'],
|
|
stdout: flutterUpstreamUrl,
|
|
),
|
|
FakeCommand(
|
|
command: const <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'HEAD',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%ad',
|
|
'--date=iso',
|
|
],
|
|
stdout: getChannelUpToDateVersion().toString(),
|
|
),
|
|
const FakeCommand(command: <String>['git', 'fetch', '--tags']),
|
|
FakeCommand(
|
|
command: const <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'@{upstream}',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%ad',
|
|
'--date=iso',
|
|
],
|
|
stdout: getChannelOutOfDateVersion().toString(),
|
|
),
|
|
const FakeCommand(
|
|
command: <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%ar',
|
|
],
|
|
stdout: '1 second ago',
|
|
),
|
|
FakeCommand(
|
|
command: const <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'HEAD',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%ad',
|
|
'--date=iso',
|
|
],
|
|
stdout: getChannelUpToDateVersion().toString(),
|
|
),
|
|
const FakeCommand(
|
|
command: <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%ar',
|
|
'abcdefg',
|
|
],
|
|
stdout: '2 seconds ago',
|
|
),
|
|
FakeCommand(
|
|
command: const <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'abcdefg',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%ad',
|
|
'--date=iso',
|
|
],
|
|
stdout: getChannelUpToDateVersion().toString(),
|
|
),
|
|
]);
|
|
|
|
final flutterVersion = FlutterVersion(
|
|
clock: _testClock,
|
|
fs: fs,
|
|
flutterRoot: flutterRoot,
|
|
git: git,
|
|
);
|
|
await flutterVersion.checkFlutterVersionFreshness();
|
|
expect(flutterVersion.channel, channel);
|
|
expect(flutterVersion.repositoryUrl, flutterUpstreamUrl);
|
|
expect(flutterVersion.frameworkRevision, '1234abcd');
|
|
expect(flutterVersion.frameworkRevisionShort, '1234abcd');
|
|
expect(flutterVersion.frameworkVersion, '0.0.0-unknown');
|
|
expect(
|
|
flutterVersion.toString(),
|
|
'Flutter • channel $channel • $flutterUpstreamUrl\n'
|
|
'Framework • revision 1234abcd (1 second ago) • ${getChannelUpToDateVersion()}\n'
|
|
'Engine • revision abcdefg (2 seconds ago) • ${getChannelUpToDateVersion()}\n'
|
|
'Tools • Dart 2.12.0 • DevTools 2.8.0',
|
|
);
|
|
expect(flutterVersion.frameworkAge, '1 second ago');
|
|
expect(flutterVersion.getVersionString(), '$channel/1234abcd');
|
|
expect(flutterVersion.getBranchName(), channel);
|
|
expect(flutterVersion.getVersionString(redactUnknownBranches: true), '$channel/1234abcd');
|
|
expect(flutterVersion.getBranchName(redactUnknownBranches: true), channel);
|
|
|
|
expect(testLogger.statusText, isEmpty);
|
|
expect(processManager, hasNoRemainingExpectations);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
ProcessManager: () => processManager,
|
|
Cache: () => cache,
|
|
Logger: () => testLogger,
|
|
},
|
|
);
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/142521
|
|
testUsingContext(
|
|
'does not remove version files when fetching tags',
|
|
() async {
|
|
const flutterUpstreamUrl = 'https://github.com/flutter/flutter.git';
|
|
processManager.addCommands(<FakeCommand>[
|
|
const FakeCommand(
|
|
command: <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%H',
|
|
],
|
|
stdout: '1234abcd',
|
|
),
|
|
const FakeCommand(command: <String>['git', 'symbolic-ref', '--short', 'HEAD']),
|
|
const FakeCommand(
|
|
command: <String>[
|
|
'git',
|
|
'fetch',
|
|
'https://github.com/flutter/flutter.git',
|
|
'--tags',
|
|
'-f',
|
|
],
|
|
),
|
|
const FakeCommand(command: <String>['git', 'tag', '--points-at', '1234abcd']),
|
|
...mockGitTagHistory(
|
|
latestTag: '0.1.2-3',
|
|
headRef: '1234abcd',
|
|
ancestorRef: 'abcd1234',
|
|
commitsBetweenRefs: 170,
|
|
),
|
|
FakeCommand(
|
|
command: const <String>['git', 'symbolic-ref', '--short', 'HEAD'],
|
|
stdout: channel,
|
|
),
|
|
FakeCommand(
|
|
command: const <String>[
|
|
'git',
|
|
'rev-parse',
|
|
'--abbrev-ref',
|
|
'--symbolic',
|
|
'@{upstream}',
|
|
],
|
|
stdout: 'origin/$channel',
|
|
),
|
|
const FakeCommand(
|
|
command: <String>['git', 'ls-remote', '--get-url', 'origin'],
|
|
stdout: flutterUpstreamUrl,
|
|
),
|
|
FakeCommand(
|
|
command: const <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'HEAD',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%ad',
|
|
'--date=iso',
|
|
],
|
|
stdout: getChannelUpToDateVersion().toString(),
|
|
),
|
|
FakeCommand(
|
|
command: const <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'abcdefg',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%ad',
|
|
'--date=iso',
|
|
],
|
|
stdout: getChannelUpToDateVersion().toString(),
|
|
),
|
|
FakeCommand(
|
|
command: const <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'HEAD',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%ad',
|
|
'--date=iso',
|
|
],
|
|
stdout: getChannelUpToDateVersion().toString(),
|
|
),
|
|
const FakeCommand(command: <String>['git', 'fetch', '--tags']),
|
|
FakeCommand(
|
|
command: const <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'@{upstream}',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%ad',
|
|
'--date=iso',
|
|
],
|
|
stdout: getChannelUpToDateVersion().toString(),
|
|
),
|
|
const FakeCommand(
|
|
command: <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%ar',
|
|
],
|
|
stdout: '1 second ago',
|
|
),
|
|
FakeCommand(
|
|
command: const <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'HEAD',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%ad',
|
|
'--date=iso',
|
|
],
|
|
stdout: getChannelUpToDateVersion().toString(),
|
|
),
|
|
const FakeCommand(
|
|
command: <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%ar',
|
|
'abcdefg',
|
|
],
|
|
stdout: '2 seconds ago',
|
|
),
|
|
]);
|
|
|
|
final flutterVersion = FlutterVersion(
|
|
clock: _testClock,
|
|
fs: fs,
|
|
flutterRoot: flutterRoot,
|
|
fetchTags: true,
|
|
git: git,
|
|
);
|
|
await flutterVersion.checkFlutterVersionFreshness();
|
|
|
|
// Verify the version files exist and have been repopulated after the fetch.
|
|
expect(FlutterVersion.getVersionFile(fs, flutterRoot), exists); // flutter.version.json
|
|
|
|
expect(flutterVersion.channel, channel);
|
|
expect(flutterVersion.repositoryUrl, flutterUpstreamUrl);
|
|
expect(flutterVersion.frameworkRevision, '1234abcd');
|
|
expect(flutterVersion.frameworkRevisionShort, '1234abcd');
|
|
expect(flutterVersion.frameworkVersion, '0.0.0-unknown');
|
|
expect(
|
|
flutterVersion.toString(),
|
|
'Flutter • channel $channel • $flutterUpstreamUrl\n'
|
|
'Framework • revision 1234abcd (1 second ago) • ${getChannelUpToDateVersion()}\n'
|
|
'Engine • revision abcdefg (2 seconds ago) • ${getChannelUpToDateVersion()}\n'
|
|
'Tools • Dart 2.12.0 • DevTools 2.8.0',
|
|
);
|
|
expect(flutterVersion.frameworkAge, '1 second ago');
|
|
expect(flutterVersion.getVersionString(), '$channel/1234abcd');
|
|
expect(flutterVersion.getBranchName(), channel);
|
|
expect(flutterVersion.getVersionString(redactUnknownBranches: true), '$channel/1234abcd');
|
|
expect(flutterVersion.getBranchName(redactUnknownBranches: true), channel);
|
|
|
|
expect(testLogger.statusText, isEmpty);
|
|
expect(processManager, hasNoRemainingExpectations);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
ProcessManager: () => processManager,
|
|
Cache: () => cache,
|
|
Logger: () => testLogger,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'does not crash when git log outputs malformed output',
|
|
() async {
|
|
const flutterUpstreamUrl = 'https://github.com/flutter/flutter.git';
|
|
|
|
final malformedGitLogOutput =
|
|
'${getChannelUpToDateVersion()}[0x7FF9E2A75000] ANOMALY: meaningless REX prefix used';
|
|
processManager.addCommands(<FakeCommand>[
|
|
const FakeCommand(
|
|
command: <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%H',
|
|
],
|
|
stdout: '1234abcd',
|
|
),
|
|
const FakeCommand(command: <String>['git', 'tag', '--points-at', '1234abcd']),
|
|
...mockGitTagHistory(
|
|
latestTag: '0.1.2-3',
|
|
headRef: '1234abcd',
|
|
ancestorRef: 'abcd1234',
|
|
commitsBetweenRefs: 170,
|
|
),
|
|
FakeCommand(
|
|
command: const <String>['git', 'symbolic-ref', '--short', 'HEAD'],
|
|
stdout: channel,
|
|
),
|
|
FakeCommand(
|
|
command: const <String>[
|
|
'git',
|
|
'rev-parse',
|
|
'--abbrev-ref',
|
|
'--symbolic',
|
|
'@{upstream}',
|
|
],
|
|
stdout: 'origin/$channel',
|
|
),
|
|
const FakeCommand(
|
|
command: <String>['git', 'ls-remote', '--get-url', 'origin'],
|
|
stdout: flutterUpstreamUrl,
|
|
),
|
|
FakeCommand(
|
|
command: const <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'HEAD',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%ad',
|
|
'--date=iso',
|
|
],
|
|
stdout: malformedGitLogOutput,
|
|
),
|
|
]);
|
|
|
|
final flutterVersion = FlutterVersion(
|
|
clock: _testClock,
|
|
fs: fs,
|
|
flutterRoot: flutterRoot,
|
|
git: git,
|
|
);
|
|
await flutterVersion.checkFlutterVersionFreshness();
|
|
|
|
expect(testLogger.statusText, isEmpty);
|
|
expect(processManager, hasNoRemainingExpectations);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
ProcessManager: () => processManager,
|
|
Cache: () => cache,
|
|
Logger: () => testLogger,
|
|
},
|
|
);
|
|
|
|
testWithoutContext(
|
|
'prints nothing when Flutter installation looks out-of-date but is actually up-to-date',
|
|
() async {
|
|
final flutterVersion = FakeFlutterVersion(branch: channel);
|
|
final stamp = VersionCheckStamp(
|
|
lastTimeVersionWasChecked: _stampOutOfDate,
|
|
lastKnownRemoteVersion: getChannelOutOfDateVersion(),
|
|
);
|
|
cache.versionStamp = json.encode(stamp);
|
|
|
|
await VersionFreshnessValidator(
|
|
version: flutterVersion,
|
|
cache: cache,
|
|
clock: _testClock,
|
|
logger: testLogger,
|
|
localFrameworkCommitDate: getChannelOutOfDateVersion(),
|
|
latestFlutterCommitDate: getChannelOutOfDateVersion(),
|
|
).run();
|
|
|
|
expect(testLogger.statusText, isEmpty);
|
|
},
|
|
);
|
|
|
|
testWithoutContext('does not ping server when version stamp is up-to-date', () async {
|
|
final flutterVersion = FakeFlutterVersion(branch: channel);
|
|
final stamp = VersionCheckStamp(
|
|
lastTimeVersionWasChecked: _stampUpToDate,
|
|
lastKnownRemoteVersion: getChannelUpToDateVersion(),
|
|
);
|
|
cache.versionStamp = json.encode(stamp);
|
|
|
|
await VersionFreshnessValidator(
|
|
version: flutterVersion,
|
|
cache: cache,
|
|
clock: _testClock,
|
|
logger: testLogger,
|
|
localFrameworkCommitDate: getChannelOutOfDateVersion(),
|
|
latestFlutterCommitDate: getChannelUpToDateVersion(),
|
|
).run();
|
|
|
|
expect(testLogger.statusText, contains('A new version of Flutter is available!'));
|
|
expect(cache.setVersionStamp, true);
|
|
});
|
|
|
|
testWithoutContext('does not print warning if printed recently', () async {
|
|
final flutterVersion = FakeFlutterVersion(branch: channel);
|
|
final stamp = VersionCheckStamp(
|
|
lastTimeVersionWasChecked: _stampUpToDate,
|
|
lastKnownRemoteVersion: getChannelUpToDateVersion(),
|
|
lastTimeWarningWasPrinted: _testClock.now(),
|
|
);
|
|
cache.versionStamp = json.encode(stamp);
|
|
|
|
await VersionFreshnessValidator(
|
|
version: flutterVersion,
|
|
cache: cache,
|
|
clock: _testClock,
|
|
logger: testLogger,
|
|
localFrameworkCommitDate: getChannelOutOfDateVersion(),
|
|
latestFlutterCommitDate: getChannelUpToDateVersion(),
|
|
).run();
|
|
|
|
expect(testLogger.statusText, isEmpty);
|
|
});
|
|
|
|
testWithoutContext('pings server when version stamp is missing', () async {
|
|
final flutterVersion = FakeFlutterVersion(branch: channel);
|
|
final logger = BufferLogger.test();
|
|
cache.versionStamp = '{}';
|
|
|
|
await VersionFreshnessValidator(
|
|
version: flutterVersion,
|
|
cache: cache,
|
|
clock: _testClock,
|
|
logger: logger,
|
|
localFrameworkCommitDate: getChannelOutOfDateVersion(),
|
|
latestFlutterCommitDate: getChannelUpToDateVersion(),
|
|
).run();
|
|
|
|
expect(logger.statusText, contains('A new version of Flutter is available!'));
|
|
expect(cache.setVersionStamp, true);
|
|
});
|
|
|
|
testWithoutContext('pings server when version stamp is out-of-date', () async {
|
|
final flutterVersion = FakeFlutterVersion(branch: channel);
|
|
final stamp = VersionCheckStamp(
|
|
lastTimeVersionWasChecked: _stampOutOfDate,
|
|
lastKnownRemoteVersion: _testClock.ago(const Duration(days: 2)),
|
|
);
|
|
cache.versionStamp = json.encode(stamp);
|
|
|
|
await VersionFreshnessValidator(
|
|
version: flutterVersion,
|
|
cache: cache,
|
|
clock: _testClock,
|
|
logger: testLogger,
|
|
localFrameworkCommitDate: getChannelOutOfDateVersion(),
|
|
latestFlutterCommitDate: getChannelUpToDateVersion(),
|
|
).run();
|
|
|
|
expect(testLogger.statusText, contains('A new version of Flutter is available!'));
|
|
});
|
|
|
|
testWithoutContext(
|
|
'does not print warning when unable to connect to server if not out of date',
|
|
() async {
|
|
final flutterVersion = FakeFlutterVersion(branch: channel);
|
|
cache.versionStamp = '{}';
|
|
|
|
await VersionFreshnessValidator(
|
|
version: flutterVersion,
|
|
cache: cache,
|
|
clock: _testClock,
|
|
logger: testLogger,
|
|
localFrameworkCommitDate: getChannelUpToDateVersion(),
|
|
// latestFlutterCommitDate defaults to null because we failed to get remote version
|
|
).run();
|
|
|
|
expect(testLogger.statusText, isEmpty);
|
|
},
|
|
);
|
|
|
|
testWithoutContext(
|
|
'prints warning when unable to connect to server if really out of date',
|
|
() async {
|
|
final flutterVersion = FakeFlutterVersion(branch: channel);
|
|
final stamp = VersionCheckStamp(
|
|
lastTimeVersionWasChecked: _stampOutOfDate,
|
|
lastKnownRemoteVersion: _testClock.ago(const Duration(days: 2)),
|
|
);
|
|
cache.versionStamp = json.encode(stamp);
|
|
|
|
await VersionFreshnessValidator(
|
|
version: flutterVersion,
|
|
cache: cache,
|
|
clock: _testClock,
|
|
logger: testLogger,
|
|
localFrameworkCommitDate: getChannelOutOfDateVersion(),
|
|
// latestFlutterCommitDate defaults to null because we failed to get remote version
|
|
).run();
|
|
|
|
final Duration frameworkAge = _testClock.now().difference(getChannelOutOfDateVersion());
|
|
expect(
|
|
testLogger.statusText,
|
|
contains('WARNING: your installation of Flutter is ${frameworkAge.inDays} days old.'),
|
|
);
|
|
},
|
|
);
|
|
|
|
group('$VersionCheckStamp for $channel', () {
|
|
void expectDefault(VersionCheckStamp stamp) {
|
|
expect(stamp.lastKnownRemoteVersion, isNull);
|
|
expect(stamp.lastTimeVersionWasChecked, isNull);
|
|
expect(stamp.lastTimeWarningWasPrinted, isNull);
|
|
}
|
|
|
|
testWithoutContext('loads blank when stamp file missing', () async {
|
|
cache.versionStamp = null;
|
|
|
|
expectDefault(await VersionCheckStamp.load(cache, BufferLogger.test()));
|
|
});
|
|
|
|
testWithoutContext('loads blank when stamp file is malformed JSON', () async {
|
|
cache.versionStamp = '<';
|
|
|
|
expectDefault(await VersionCheckStamp.load(cache, BufferLogger.test()));
|
|
});
|
|
|
|
testWithoutContext('loads blank when stamp file is well-formed but invalid JSON', () async {
|
|
cache.versionStamp = '[]';
|
|
|
|
expectDefault(await VersionCheckStamp.load(cache, BufferLogger.test()));
|
|
});
|
|
|
|
testWithoutContext('loads valid JSON', () async {
|
|
final value =
|
|
'''
|
|
{
|
|
"lastKnownRemoteVersion": "${_testClock.ago(const Duration(days: 1))}",
|
|
"lastTimeVersionWasChecked": "${_testClock.ago(const Duration(days: 2))}",
|
|
"lastTimeWarningWasPrinted": "${_testClock.now()}"
|
|
}
|
|
''';
|
|
cache.versionStamp = value;
|
|
|
|
final VersionCheckStamp stamp = await VersionCheckStamp.load(cache, BufferLogger.test());
|
|
|
|
expect(stamp.lastKnownRemoteVersion, _testClock.ago(const Duration(days: 1)));
|
|
expect(stamp.lastTimeVersionWasChecked, _testClock.ago(const Duration(days: 2)));
|
|
expect(stamp.lastTimeWarningWasPrinted, _testClock.now());
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
group('VersionUpstreamValidator', () {
|
|
const flutterStandardUrlDotGit = 'https://github.com/flutter/flutter.git';
|
|
const flutterNonStandardUrlDotGit = 'https://githubmirror.com/flutter/flutter.git';
|
|
const flutterStandardSshUrlDotGit = 'git@github.com:flutter/flutter.git';
|
|
const flutterFullSshUrlDotGit = 'ssh://git@github.com/flutter/flutter.git';
|
|
|
|
VersionCheckError? runUpstreamValidator({String? versionUpstreamUrl, String? flutterGitUrl}) {
|
|
final Platform testPlatform = FakePlatform(
|
|
environment: <String, String>{'FLUTTER_GIT_URL': ?flutterGitUrl},
|
|
);
|
|
return VersionUpstreamValidator(
|
|
version: FakeFlutterVersion(repositoryUrl: versionUpstreamUrl),
|
|
platform: testPlatform,
|
|
).run();
|
|
}
|
|
|
|
testWithoutContext('returns error if repository url is null', () {
|
|
final VersionCheckError error = runUpstreamValidator(
|
|
// repositoryUrl is null by default
|
|
)!;
|
|
expect(error, isNotNull);
|
|
expect(
|
|
error.message,
|
|
contains(
|
|
'The tool could not determine the remote upstream which is being tracked by the SDK.',
|
|
),
|
|
);
|
|
});
|
|
|
|
testWithoutContext(
|
|
'does not return error at standard remote url with FLUTTER_GIT_URL unset',
|
|
() {
|
|
expect(runUpstreamValidator(versionUpstreamUrl: flutterStandardUrlDotGit), isNull);
|
|
},
|
|
);
|
|
|
|
testWithoutContext('returns error at non-standard remote url with FLUTTER_GIT_URL unset', () {
|
|
final VersionCheckError error = runUpstreamValidator(
|
|
versionUpstreamUrl: flutterNonStandardUrlDotGit,
|
|
)!;
|
|
expect(error, isNotNull);
|
|
expect(
|
|
error.message,
|
|
contains(
|
|
'The Flutter SDK is tracking a non-standard remote "$flutterNonStandardUrlDotGit".\n'
|
|
'Set the environment variable "FLUTTER_GIT_URL" to "$flutterNonStandardUrlDotGit". '
|
|
'If this is intentional, it is recommended to use "git" directly to manage the SDK.',
|
|
),
|
|
);
|
|
});
|
|
|
|
testWithoutContext(
|
|
'does not return error at non-standard remote url with FLUTTER_GIT_URL set',
|
|
() {
|
|
expect(
|
|
runUpstreamValidator(
|
|
versionUpstreamUrl: flutterNonStandardUrlDotGit,
|
|
flutterGitUrl: flutterNonStandardUrlDotGit,
|
|
),
|
|
isNull,
|
|
);
|
|
},
|
|
);
|
|
|
|
testWithoutContext('respects FLUTTER_GIT_URL even if upstream remote url is standard', () {
|
|
final VersionCheckError error = runUpstreamValidator(
|
|
versionUpstreamUrl: flutterStandardUrlDotGit,
|
|
flutterGitUrl: flutterNonStandardUrlDotGit,
|
|
)!;
|
|
expect(error, isNotNull);
|
|
expect(
|
|
error.message,
|
|
contains(
|
|
'The Flutter SDK is tracking "$flutterStandardUrlDotGit" but "FLUTTER_GIT_URL" is set to "$flutterNonStandardUrlDotGit".\n'
|
|
'Either remove "FLUTTER_GIT_URL" from the environment or set it to "$flutterStandardUrlDotGit". '
|
|
'If this is intentional, it is recommended to use "git" directly to manage the SDK.',
|
|
),
|
|
);
|
|
});
|
|
|
|
testWithoutContext('does not return error at standard ssh url with FLUTTER_GIT_URL unset', () {
|
|
expect(runUpstreamValidator(versionUpstreamUrl: flutterStandardSshUrlDotGit), isNull);
|
|
});
|
|
|
|
testWithoutContext('does not return error at full ssh url with FLUTTER_GIT_URL unset', () {
|
|
expect(runUpstreamValidator(versionUpstreamUrl: flutterFullSshUrlDotGit), isNull);
|
|
});
|
|
|
|
testWithoutContext('stripDotGit removes ".git" suffix if any', () {
|
|
expect(
|
|
VersionUpstreamValidator.stripDotGit('https://github.com/flutter/flutter.git'),
|
|
'https://github.com/flutter/flutter',
|
|
);
|
|
expect(
|
|
VersionUpstreamValidator.stripDotGit('https://github.com/flutter/flutter'),
|
|
'https://github.com/flutter/flutter',
|
|
);
|
|
expect(
|
|
VersionUpstreamValidator.stripDotGit('git@github.com:flutter/flutter.git'),
|
|
'git@github.com:flutter/flutter',
|
|
);
|
|
expect(
|
|
VersionUpstreamValidator.stripDotGit('git@github.com:flutter/flutter'),
|
|
'git@github.com:flutter/flutter',
|
|
);
|
|
expect(
|
|
VersionUpstreamValidator.stripDotGit('https://githubmirror.com/flutter/flutter.git.git'),
|
|
'https://githubmirror.com/flutter/flutter.git',
|
|
);
|
|
expect(
|
|
VersionUpstreamValidator.stripDotGit('https://githubmirror.com/flutter/flutter.gitgit'),
|
|
'https://githubmirror.com/flutter/flutter.gitgit',
|
|
);
|
|
});
|
|
});
|
|
|
|
testUsingContext(
|
|
'version handles unknown branch',
|
|
() async {
|
|
processManager.addCommands(<FakeCommand>[
|
|
const FakeCommand(
|
|
command: <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%H',
|
|
],
|
|
stdout: '1234abcd',
|
|
),
|
|
const FakeCommand(command: <String>['git', 'tag', '--points-at', '1234abcd']),
|
|
...mockGitTagHistory(
|
|
latestTag: '0.1.2-3',
|
|
headRef: '1234abcd',
|
|
ancestorRef: 'abcd1234',
|
|
commitsBetweenRefs: 170,
|
|
),
|
|
const FakeCommand(
|
|
command: <String>['git', 'symbolic-ref', '--short', 'HEAD'],
|
|
stdout: 'feature-branch',
|
|
),
|
|
]);
|
|
|
|
final fs = MemoryFileSystem.test();
|
|
final flutterVersion = FlutterVersion(
|
|
clock: _testClock,
|
|
fs: fs,
|
|
flutterRoot: '/path/to/flutter',
|
|
git: git,
|
|
);
|
|
expect(flutterVersion.channel, '[user-branch]');
|
|
expect(flutterVersion.getVersionString(), 'feature-branch/1234abcd');
|
|
expect(flutterVersion.getBranchName(), 'feature-branch');
|
|
expect(
|
|
flutterVersion.getVersionString(redactUnknownBranches: true),
|
|
'[user-branch]/1234abcd',
|
|
);
|
|
expect(flutterVersion.getBranchName(redactUnknownBranches: true), '[user-branch]');
|
|
|
|
expect(processManager, hasNoRemainingExpectations);
|
|
},
|
|
overrides: <Type, Generator>{ProcessManager: () => processManager, Cache: () => cache},
|
|
);
|
|
|
|
testUsingContext(
|
|
'ensureVersionFile() writes version information to disk',
|
|
() async {
|
|
processManager.addCommands(<FakeCommand>[
|
|
const FakeCommand(
|
|
command: <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%H',
|
|
],
|
|
stdout: '1234abcd',
|
|
),
|
|
const FakeCommand(command: <String>['git', 'tag', '--points-at', '1234abcd']),
|
|
...mockGitTagHistory(
|
|
latestTag: '0.1.2-3',
|
|
headRef: '1234abcd',
|
|
ancestorRef: 'abcd1234',
|
|
commitsBetweenRefs: 170,
|
|
),
|
|
const FakeCommand(
|
|
command: <String>['git', 'symbolic-ref', '--short', 'HEAD'],
|
|
stdout: 'feature-branch',
|
|
),
|
|
const FakeCommand(
|
|
command: <String>['git', 'rev-parse', '--abbrev-ref', '--symbolic', '@{upstream}'],
|
|
),
|
|
FakeCommand(
|
|
command: const <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'HEAD',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%ad',
|
|
'--date=iso',
|
|
],
|
|
stdout: _testClock
|
|
.ago(VersionFreshnessValidator.versionAgeConsideredUpToDate('stable') ~/ 2)
|
|
.toString(),
|
|
),
|
|
FakeCommand(
|
|
command: const <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'abcdefg',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%ad',
|
|
'--date=iso',
|
|
],
|
|
stdout: _testClock
|
|
.ago(VersionFreshnessValidator.versionAgeConsideredUpToDate('stable') ~/ 2)
|
|
.toString(),
|
|
),
|
|
]);
|
|
|
|
final fs = MemoryFileSystem.test();
|
|
final Directory flutterRoot = fs.directory('/path/to/flutter');
|
|
flutterRoot.childDirectory('bin').childDirectory('cache').createSync(recursive: true);
|
|
final flutterVersion = FlutterVersion(
|
|
clock: _testClock,
|
|
fs: fs,
|
|
flutterRoot: flutterRoot.path,
|
|
git: git,
|
|
);
|
|
|
|
final File versionFile = fs.file('/path/to/flutter/bin/cache/flutter.version.json');
|
|
expect(versionFile.existsSync(), isFalse);
|
|
|
|
flutterVersion.ensureVersionFile();
|
|
expect(versionFile.existsSync(), isTrue);
|
|
expect(versionFile.readAsStringSync(), '''
|
|
{
|
|
"frameworkVersion": "0.0.0-unknown",
|
|
"channel": "[user-branch]",
|
|
"repositoryUrl": "unknown source",
|
|
"frameworkRevision": "1234abcd",
|
|
"frameworkCommitDate": "2014-10-02 00:00:00.000Z",
|
|
"engineRevision": "abcdefg",
|
|
"engineCommitDate": "2014-10-02 00:00:00.000Z",
|
|
"dartSdkVersion": "2.12.0",
|
|
"devToolsVersion": "2.8.0",
|
|
"flutterVersion": "0.0.0-unknown"
|
|
}''');
|
|
expect(processManager, hasNoRemainingExpectations);
|
|
},
|
|
overrides: <Type, Generator>{ProcessManager: () => processManager, Cache: () => cache},
|
|
);
|
|
|
|
testUsingContext(
|
|
'version does not call git if a .version.json file exists',
|
|
() async {
|
|
final fs = MemoryFileSystem.test();
|
|
final Directory flutterRoot = fs.directory('/path/to/flutter');
|
|
final Directory cacheDir = flutterRoot.childDirectory('bin').childDirectory('cache')
|
|
..createSync(recursive: true);
|
|
const devToolsVersion = '0000000';
|
|
const versionJson = <String, Object>{
|
|
'channel': 'stable',
|
|
'frameworkVersion': '1.2.3',
|
|
'repositoryUrl': 'https://github.com/flutter/flutter.git',
|
|
'frameworkRevision': '1234abcd',
|
|
'frameworkCommitDate': '2023-04-28 12:34:56 -0400',
|
|
'engineRevision': 'deadbeef',
|
|
'dartSdkVersion': 'deadbeef2',
|
|
'devToolsVersion': devToolsVersion,
|
|
'flutterVersion': 'foo',
|
|
};
|
|
cacheDir.childFile('flutter.version.json').writeAsStringSync(jsonEncode(versionJson));
|
|
final flutterVersion = FlutterVersion(
|
|
clock: _testClock,
|
|
fs: fs,
|
|
flutterRoot: flutterRoot.path,
|
|
git: git,
|
|
);
|
|
expect(flutterVersion.channel, 'stable');
|
|
expect(flutterVersion.getVersionString(), 'stable/1.2.3');
|
|
expect(flutterVersion.getBranchName(), 'stable');
|
|
expect(flutterVersion.dartSdkVersion, 'deadbeef2');
|
|
expect(flutterVersion.devToolsVersion, devToolsVersion);
|
|
expect(flutterVersion.engineRevision, 'deadbeef');
|
|
|
|
expect(processManager, hasNoRemainingExpectations);
|
|
},
|
|
overrides: <Type, Generator>{ProcessManager: () => processManager, Cache: () => cache},
|
|
);
|
|
|
|
testUsingContext(
|
|
'_FlutterVersionFromFile.ensureVersionFile ensures legacy version file exists',
|
|
() async {
|
|
final fs = MemoryFileSystem.test();
|
|
final Directory flutterRoot = fs.directory('/path/to/flutter');
|
|
final Directory cacheDir = flutterRoot.childDirectory('bin').childDirectory('cache')
|
|
..createSync(recursive: true);
|
|
const devToolsVersion = '0000000';
|
|
final File legacyVersionFile = flutterRoot.childFile('version');
|
|
const versionJson = <String, Object>{
|
|
'channel': 'stable',
|
|
'frameworkVersion': '1.2.3',
|
|
'repositoryUrl': 'https://github.com/flutter/flutter.git',
|
|
'frameworkRevision': '1234abcd',
|
|
'frameworkCommitDate': '2023-04-28 12:34:56 -0400',
|
|
'engineRevision': 'deadbeef',
|
|
'dartSdkVersion': 'deadbeef2',
|
|
'devToolsVersion': devToolsVersion,
|
|
'flutterVersion': 'foo',
|
|
};
|
|
cacheDir.childFile('flutter.version.json').writeAsStringSync(jsonEncode(versionJson));
|
|
expect(legacyVersionFile.existsSync(), isFalse);
|
|
final flutterVersion = FlutterVersion(
|
|
clock: _testClock,
|
|
fs: fs,
|
|
flutterRoot: flutterRoot.path,
|
|
git: git,
|
|
);
|
|
flutterVersion.ensureVersionFile();
|
|
expect(legacyVersionFile.existsSync(), isTrue);
|
|
expect(legacyVersionFile.readAsStringSync(), '1.2.3');
|
|
},
|
|
overrides: <Type, Generator>{
|
|
ProcessManager: () => processManager,
|
|
Cache: () => cache,
|
|
// ignore: avoid_redundant_argument_values
|
|
FeatureFlags: () => TestFeatureFlags(isOmitLegacyVersionFileEnabled: false),
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'_FlutterVersionFromFile ignores engineCommitDate if historically omitted',
|
|
() async {
|
|
final fs = MemoryFileSystem.test();
|
|
final Directory flutterRoot = fs.directory('/path/to/flutter');
|
|
final Directory cacheDir = flutterRoot.childDirectory('bin').childDirectory('cache')
|
|
..createSync(recursive: true);
|
|
|
|
const versionJson = <String, Object>{
|
|
'channel': 'stable',
|
|
'frameworkVersion': '1.2.3',
|
|
'repositoryUrl': 'https://github.com/flutter/flutter.git',
|
|
'frameworkRevision': '1234abcd',
|
|
'frameworkCommitDate': '2023-04-28 12:34:56 -0400',
|
|
'engineRevision': 'deadbeef',
|
|
'dartSdkVersion': 'deadbeef2',
|
|
'devToolsVersion': '0000000',
|
|
'flutterVersion': 'foo',
|
|
};
|
|
cacheDir.childFile('flutter.version.json').writeAsStringSync(jsonEncode(versionJson));
|
|
|
|
processManager.addCommands(<FakeCommand>[
|
|
const FakeCommand(
|
|
command: <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%ar',
|
|
],
|
|
stdout: '1 second ago',
|
|
),
|
|
const FakeCommand(
|
|
command: <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%ar',
|
|
'deadbeef',
|
|
],
|
|
stdout: '1 second ago',
|
|
),
|
|
]);
|
|
|
|
final flutterVersion = FlutterVersion(
|
|
clock: _testClock,
|
|
fs: fs,
|
|
flutterRoot: flutterRoot.path,
|
|
git: git,
|
|
);
|
|
expect(flutterVersion.engineCommitDate, isNull);
|
|
expect(flutterVersion.toJson(), isNot(contains('engineCommitDate')));
|
|
expect(flutterVersion.toString(), contains('Engine • revision deadbeef (1 second ago)\n'));
|
|
},
|
|
overrides: <Type, Generator>{ProcessManager: () => processManager, Cache: () => cache},
|
|
);
|
|
|
|
testUsingContext(
|
|
'FlutterVersion() falls back to git if .version.json is malformed',
|
|
() async {
|
|
final fs = MemoryFileSystem.test();
|
|
final Directory flutterRoot = fs.directory(fs.path.join('path', 'to', 'flutter'));
|
|
final Directory cacheDir = flutterRoot.childDirectory('bin').childDirectory('cache')
|
|
..createSync(recursive: true);
|
|
final File versionFile = cacheDir.childFile('flutter.version.json')..writeAsStringSync('{');
|
|
|
|
processManager.addCommands(<FakeCommand>[
|
|
const FakeCommand(
|
|
command: <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%H',
|
|
],
|
|
stdout: '1234abcd',
|
|
),
|
|
const FakeCommand(command: <String>['git', 'tag', '--points-at', '1234abcd']),
|
|
...mockGitTagHistory(
|
|
latestTag: '0.1.2-3',
|
|
headRef: '1234abcd',
|
|
ancestorRef: 'abcd1234',
|
|
commitsBetweenRefs: 170,
|
|
),
|
|
const FakeCommand(
|
|
command: <String>['git', 'symbolic-ref', '--short', 'HEAD'],
|
|
stdout: 'feature-branch',
|
|
),
|
|
const FakeCommand(
|
|
command: <String>['git', 'rev-parse', '--abbrev-ref', '--symbolic', '@{upstream}'],
|
|
stdout: 'feature-branch',
|
|
),
|
|
FakeCommand(
|
|
command: const <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'HEAD',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%ad',
|
|
'--date=iso',
|
|
],
|
|
stdout: _testClock
|
|
.ago(VersionFreshnessValidator.versionAgeConsideredUpToDate('stable') ~/ 2)
|
|
.toString(),
|
|
),
|
|
FakeCommand(
|
|
command: const <String>[
|
|
'git',
|
|
'-c',
|
|
'log.showSignature=false',
|
|
'log',
|
|
'abcdefg',
|
|
'-n',
|
|
'1',
|
|
'--pretty=format:%ad',
|
|
'--date=iso',
|
|
],
|
|
stdout: _testClock
|
|
.ago(VersionFreshnessValidator.versionAgeConsideredUpToDate('stable') ~/ 2)
|
|
.toString(),
|
|
),
|
|
]);
|
|
|
|
// version file exists in a malformed state
|
|
expect(versionFile.existsSync(), isTrue);
|
|
final flutterVersion = FlutterVersion(
|
|
clock: _testClock,
|
|
fs: fs,
|
|
flutterRoot: flutterRoot.path,
|
|
git: git,
|
|
);
|
|
|
|
// version file was deleted because it couldn't be parsed
|
|
expect(versionFile.existsSync(), isFalse);
|
|
// version file was written to disk
|
|
flutterVersion.ensureVersionFile();
|
|
expect(processManager, hasNoRemainingExpectations);
|
|
expect(versionFile.existsSync(), isTrue);
|
|
},
|
|
overrides: <Type, Generator>{ProcessManager: () => processManager, Cache: () => cache},
|
|
);
|
|
|
|
testUsingContext(
|
|
'legacy version file is still supported',
|
|
() {
|
|
final fs = MemoryFileSystem.test();
|
|
final Directory flutterRoot = fs.directory(fs.path.join('path', 'to', 'flutter'));
|
|
flutterRoot.childDirectory('bin').childDirectory('cache').createSync(recursive: true);
|
|
final File legacyVersionFile = flutterRoot.childFile('version');
|
|
|
|
final flutterVersion = FlutterVersion(
|
|
clock: _testClock,
|
|
fs: fs,
|
|
flutterRoot: flutterRoot.path,
|
|
git: Git(currentPlatform: FakePlatform(), runProcessWith: globals.processUtils),
|
|
);
|
|
flutterVersion.ensureVersionFile();
|
|
|
|
expect(legacyVersionFile, exists);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
// ignore: avoid_redundant_argument_values
|
|
FeatureFlags: () => TestFeatureFlags(isOmitLegacyVersionFileEnabled: false),
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'legacy version file is no longer supported',
|
|
() {
|
|
final fs = MemoryFileSystem.test();
|
|
final Directory flutterRoot = fs.directory(fs.path.join('path', 'to', 'flutter'));
|
|
flutterRoot.childDirectory('bin').childDirectory('cache').createSync(recursive: true);
|
|
final File legacyVersionFile = flutterRoot.childFile('version');
|
|
|
|
final flutterVersion = FlutterVersion(
|
|
clock: _testClock,
|
|
fs: fs,
|
|
flutterRoot: flutterRoot.path,
|
|
git: Git(currentPlatform: FakePlatform(), runProcessWith: globals.processUtils),
|
|
);
|
|
flutterVersion.ensureVersionFile();
|
|
|
|
expect(legacyVersionFile, isNot(exists));
|
|
},
|
|
overrides: <Type, Generator>{
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
// ignore: avoid_redundant_argument_values
|
|
FeatureFlags: () => TestFeatureFlags(isOmitLegacyVersionFileEnabled: true),
|
|
},
|
|
);
|
|
|
|
testUsingContext('GitTagVersion', () {
|
|
const hash = 'abcdef';
|
|
GitTagVersion gitTagVersion;
|
|
|
|
// Master channel
|
|
gitTagVersion = GitTagVersion.parse('1.2.0-4.5.pre-13-g$hash');
|
|
expect(gitTagVersion.frameworkVersionFor(hash), '1.2.0-5.0.pre-13');
|
|
expect(gitTagVersion.gitTag, '1.2.0-4.5.pre');
|
|
expect(gitTagVersion.devVersion, 4);
|
|
expect(gitTagVersion.devPatch, 5);
|
|
|
|
// Master channel
|
|
// Format from old version files used '.' instead of '-' for the commit count.
|
|
// See https://github.com/flutter/flutter/issues/172091#issuecomment-3071202443
|
|
gitTagVersion = GitTagVersion.parse('1.2.0-4.5.pre.13');
|
|
expect(gitTagVersion.frameworkVersionFor(hash), '1.2.0-5.0.pre-13');
|
|
expect(gitTagVersion.gitTag, '1.2.0-4.5.pre');
|
|
expect(gitTagVersion.devVersion, 4);
|
|
expect(gitTagVersion.devPatch, 5);
|
|
|
|
// Stable channel
|
|
gitTagVersion = GitTagVersion.parse('1.2.3');
|
|
expect(gitTagVersion.frameworkVersionFor(hash), '1.2.3');
|
|
expect(gitTagVersion.x, 1);
|
|
expect(gitTagVersion.y, 2);
|
|
expect(gitTagVersion.z, 3);
|
|
expect(gitTagVersion.devVersion, null);
|
|
expect(gitTagVersion.devPatch, null);
|
|
|
|
// Beta channel
|
|
gitTagVersion = GitTagVersion.parse('1.2.3-4.5.pre');
|
|
expect(gitTagVersion.frameworkVersionFor(hash), '1.2.3-4.5.pre');
|
|
expect(gitTagVersion.gitTag, '1.2.3-4.5.pre');
|
|
expect(gitTagVersion.devVersion, 4);
|
|
expect(gitTagVersion.devPatch, 5);
|
|
|
|
gitTagVersion = GitTagVersion.parse('1.2.3-13-g$hash');
|
|
expect(gitTagVersion.frameworkVersionFor(hash), '1.2.4-0.0.pre-13');
|
|
expect(gitTagVersion.gitTag, '1.2.3');
|
|
expect(gitTagVersion.devVersion, null);
|
|
expect(gitTagVersion.devPatch, null);
|
|
|
|
// new tag release format, beta channel
|
|
gitTagVersion = GitTagVersion.parse('1.2.3-4.5.pre-0-g$hash');
|
|
expect(gitTagVersion.frameworkVersionFor(hash), '1.2.3-4.5.pre');
|
|
expect(gitTagVersion.gitTag, '1.2.3-4.5.pre');
|
|
expect(gitTagVersion.devVersion, 4);
|
|
expect(gitTagVersion.devPatch, 5);
|
|
|
|
// new tag release format, stable channel
|
|
gitTagVersion = GitTagVersion.parse('1.2.3-13-g$hash');
|
|
expect(gitTagVersion.frameworkVersionFor(hash), '1.2.4-0.0.pre-13');
|
|
expect(gitTagVersion.gitTag, '1.2.3');
|
|
expect(gitTagVersion.devVersion, null);
|
|
expect(gitTagVersion.devPatch, null);
|
|
|
|
// new tag release format, beta channel, old version file format
|
|
// Format from old version files used '.' instead of '-' for the commit count.
|
|
// See https://github.com/flutter/flutter/issues/172091#issuecomment-3071202443
|
|
gitTagVersion = GitTagVersion.parse('1.2.3-4.5.pre.0');
|
|
expect(gitTagVersion.frameworkVersionFor(hash), '1.2.3-4.5.pre');
|
|
expect(gitTagVersion.gitTag, '1.2.3-4.5.pre');
|
|
expect(gitTagVersion.devVersion, 4);
|
|
expect(gitTagVersion.devPatch, 5);
|
|
|
|
expect(
|
|
GitTagVersion.parse('98.76.54-32-g$hash').frameworkVersionFor(hash),
|
|
'98.76.55-0.0.pre-32',
|
|
);
|
|
// Format from old version files used '.' instead of '-' for the commit count.
|
|
// See https://github.com/flutter/flutter/issues/172091#issuecomment-3071202443
|
|
expect(
|
|
GitTagVersion.parse('98.76.54.32-g$hash').frameworkVersionFor(hash),
|
|
'98.76.55-0.0.pre-32',
|
|
);
|
|
expect(GitTagVersion.parse('10.20.30-0-g$hash').frameworkVersionFor(hash), '10.20.30');
|
|
expect(testLogger.traceText, '');
|
|
expect(
|
|
GitTagVersion.parse('v1.2.3+hotfix.1-4-g$hash').frameworkVersionFor(hash),
|
|
'0.0.0-unknown',
|
|
);
|
|
expect(GitTagVersion.parse('x1.2.3-4-g$hash').frameworkVersionFor(hash), '0.0.0-unknown');
|
|
expect(
|
|
GitTagVersion.parse('1.0.0-unknown-0-g$hash').frameworkVersionFor(hash),
|
|
'0.0.0-unknown',
|
|
);
|
|
expect(GitTagVersion.parse('beta-1-g$hash').frameworkVersionFor(hash), '0.0.0-unknown');
|
|
expect(GitTagVersion.parse('1.2.3-4-gx$hash').frameworkVersionFor(hash), '0.0.0-unknown');
|
|
expect(testLogger.statusText, '');
|
|
expect(testLogger.errorText, '');
|
|
expect(
|
|
testLogger.traceText,
|
|
stringContainsInOrder([
|
|
'Could not interpret results of "git describe": v1.2.3+hotfix.1-4-gabcdef\n',
|
|
'Could not interpret results of "git describe": x1.2.3-4-gabcdef\n',
|
|
'Could not interpret results of "git describe": 1.0.0-unknown-0-gabcdef\n',
|
|
'Could not interpret results of "git describe": beta-1-gabcdef\n',
|
|
'Could not interpret results of "git describe": 1.2.3-4-gxabcdef\n',
|
|
]),
|
|
);
|
|
}, overrides: {Logger: () => testLogger});
|
|
|
|
testUsingContext('determine reports correct stable version if HEAD is at a tag', () {
|
|
const stableTag = '1.2.3';
|
|
processManager.addCommands(<FakeCommand>[
|
|
const FakeCommand(command: <String>['git', 'tag', '--points-at', 'HEAD'], stdout: stableTag),
|
|
]);
|
|
final platform = FakePlatform();
|
|
final GitTagVersion gitTagVersion = GitTagVersion.determine(
|
|
platform,
|
|
git: git,
|
|
workingDirectory: '.',
|
|
);
|
|
expect(gitTagVersion.frameworkVersionFor('abcd1234'), stableTag);
|
|
});
|
|
|
|
testUsingContext('determine favors stable tag over beta tag if both identify HEAD', () {
|
|
const stableTag = '1.2.3';
|
|
processManager.addCommands(<FakeCommand>[
|
|
const FakeCommand(
|
|
command: <String>['git', 'tag', '--points-at', 'HEAD'],
|
|
// This tests the unlikely edge case where a beta release made it to stable without any cherry picks
|
|
stdout: '1.2.3-6.0.pre\n$stableTag',
|
|
),
|
|
]);
|
|
final platform = FakePlatform();
|
|
final GitTagVersion gitTagVersion = GitTagVersion.determine(
|
|
platform,
|
|
git: git,
|
|
workingDirectory: '.',
|
|
);
|
|
expect(gitTagVersion.frameworkVersionFor('abcd1234'), stableTag);
|
|
});
|
|
|
|
testUsingContext('determine reports correct git describe version if HEAD is not at a tag', () {
|
|
const devTag = '1.2.0-2.0.pre';
|
|
const headRevision = 'abcd1234';
|
|
processManager.addCommands(<FakeCommand>[
|
|
const FakeCommand(
|
|
command: <String>['git', 'tag', '--points-at', 'HEAD'],
|
|
// no output, since there's no tag
|
|
),
|
|
...mockGitTagHistory(
|
|
latestTag: devTag,
|
|
headRef: 'HEAD',
|
|
ancestorRef: 'abcd1234',
|
|
commitsBetweenRefs: 12,
|
|
),
|
|
]);
|
|
final platform = FakePlatform();
|
|
|
|
final GitTagVersion gitTagVersion = GitTagVersion.determine(
|
|
platform,
|
|
git: git,
|
|
workingDirectory: '.',
|
|
);
|
|
// reported version should increment the m
|
|
expect(gitTagVersion.frameworkVersionFor(headRevision), '1.2.0-3.0.pre-12');
|
|
});
|
|
|
|
testUsingContext('determine does not call fetch --tags', () {
|
|
processManager.addCommands(<FakeCommand>[
|
|
const FakeCommand(command: <String>['git', 'tag', '--points-at', 'HEAD']),
|
|
...mockGitTagHistory(
|
|
latestTag: 'v0.1.2-3',
|
|
headRef: 'HEAD',
|
|
ancestorRef: 'abcd1234',
|
|
commitsBetweenRefs: 12,
|
|
),
|
|
]);
|
|
final platform = FakePlatform();
|
|
|
|
GitTagVersion.determine(platform, workingDirectory: '.', git: git);
|
|
expect(processManager, hasNoRemainingExpectations);
|
|
});
|
|
|
|
testUsingContext('determine does not fetch tags on beta', () {
|
|
processManager.addCommands(<FakeCommand>[
|
|
const FakeCommand(
|
|
command: <String>['git', 'symbolic-ref', '--short', 'HEAD'],
|
|
stdout: 'beta',
|
|
),
|
|
const FakeCommand(command: <String>['git', 'tag', '--points-at', 'HEAD']),
|
|
...mockGitTagHistory(
|
|
latestTag: 'v0.1.2-3',
|
|
headRef: 'HEAD',
|
|
ancestorRef: 'abcd1234',
|
|
commitsBetweenRefs: 12,
|
|
),
|
|
]);
|
|
final platform = FakePlatform();
|
|
|
|
GitTagVersion.determine(platform, workingDirectory: '.', fetchTags: true, git: git);
|
|
expect(processManager, hasNoRemainingExpectations);
|
|
});
|
|
|
|
testUsingContext('determine calls fetch --tags on master', () {
|
|
processManager.addCommands(<FakeCommand>[
|
|
const FakeCommand(
|
|
command: <String>['git', 'symbolic-ref', '--short', 'HEAD'],
|
|
stdout: 'master',
|
|
),
|
|
const FakeCommand(
|
|
command: <String>['git', 'fetch', 'https://github.com/flutter/flutter.git', '--tags', '-f'],
|
|
),
|
|
const FakeCommand(command: <String>['git', 'tag', '--points-at', 'HEAD']),
|
|
...mockGitTagHistory(
|
|
latestTag: 'v0.1.2-3',
|
|
headRef: 'HEAD',
|
|
ancestorRef: 'abcd1234',
|
|
commitsBetweenRefs: 12,
|
|
),
|
|
]);
|
|
final platform = FakePlatform();
|
|
|
|
GitTagVersion.determine(platform, workingDirectory: '.', fetchTags: true, git: git);
|
|
expect(processManager, hasNoRemainingExpectations);
|
|
});
|
|
|
|
testUsingContext('determine uses overridden git url', () {
|
|
processManager.addCommands(<FakeCommand>[
|
|
const FakeCommand(
|
|
command: <String>['git', 'symbolic-ref', '--short', 'HEAD'],
|
|
stdout: 'master',
|
|
),
|
|
const FakeCommand(
|
|
command: <String>['git', 'fetch', 'https://githubmirror.com/flutter.git', '--tags', '-f'],
|
|
),
|
|
const FakeCommand(command: <String>['git', 'tag', '--points-at', 'HEAD']),
|
|
...mockGitTagHistory(
|
|
latestTag: 'v0.1.2-3',
|
|
headRef: 'HEAD',
|
|
ancestorRef: 'abcd1234',
|
|
commitsBetweenRefs: 12,
|
|
),
|
|
]);
|
|
final platform = FakePlatform(
|
|
environment: <String, String>{'FLUTTER_GIT_URL': 'https://githubmirror.com/flutter.git'},
|
|
);
|
|
|
|
GitTagVersion.determine(platform, workingDirectory: '.', fetchTags: true, git: git);
|
|
expect(processManager, hasNoRemainingExpectations);
|
|
}, overrides: {Git: () => git});
|
|
|
|
group('$FlutterEngineStampFromFile', () {
|
|
late FileSystem fs;
|
|
const flutterRoot = '/path/to/flutter';
|
|
|
|
setUpAll(() {
|
|
Cache.disableLocking();
|
|
VersionFreshnessValidator.timeToPauseToLetUserReadTheMessage = Duration.zero;
|
|
});
|
|
|
|
setUp(() {
|
|
fs = MemoryFileSystem.test();
|
|
fs.directory(flutterRoot).createSync(recursive: true);
|
|
});
|
|
|
|
test('parses expected values', () {
|
|
final File engineStampFile = fs.file(
|
|
fs.path.join(flutterRoot, 'bin', 'cache', 'engine_stamp.json'),
|
|
)..createSync(recursive: true);
|
|
engineStampFile.writeAsStringSync(
|
|
json.encode(<String, Object?>{
|
|
'build_time_ms': 1751385874000,
|
|
'git_revision': 'abcdefg',
|
|
'git_revision_date': '2014-10-02 00:00:00.000Z',
|
|
'content_hash': 'deadbeef',
|
|
}),
|
|
);
|
|
final FlutterEngineStampFromFile? result = FlutterEngineStampFromFile.tryParseFromFile(
|
|
engineStampFile,
|
|
);
|
|
expect(result, isNotNull);
|
|
expect(result!.buildDate, DateTime.fromMillisecondsSinceEpoch(1751385874000));
|
|
expect(result.gitRevision, 'abcdefg');
|
|
expect(result.gitRevisionDate, DateTime.parse('2014-10-02 00:00:00.000Z'));
|
|
expect(result.contentHash, 'deadbeef');
|
|
});
|
|
});
|
|
}
|
|
|
|
class FakeCache extends Fake implements Cache {
|
|
String? versionStamp;
|
|
bool setVersionStamp = false;
|
|
|
|
@override
|
|
String get engineRevision => 'abcdefg';
|
|
|
|
@override
|
|
String get devToolsVersion => '2.8.0';
|
|
|
|
@override
|
|
String get dartSdkVersion => '2.12.0';
|
|
|
|
@override
|
|
void checkLockAcquired() {}
|
|
|
|
@override
|
|
String? getStampFor(String artifactName) {
|
|
if (artifactName == VersionCheckStamp.flutterVersionCheckStampFile) {
|
|
return versionStamp;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@override
|
|
void setStampFor(String artifactName, String version) {
|
|
if (artifactName == VersionCheckStamp.flutterVersionCheckStampFile) {
|
|
setVersionStamp = true;
|
|
}
|
|
}
|
|
}
|