Kate Lovett 9d96df2364
Modernize framework lints (#179089)
WIP

Commits separated as follows:
- Update lints in analysis_options files
- Run `dart fix --apply`
- Clean up leftover analysis issues 
- Run `dart format .` in the right places.

Local analysis and testing passes. Checking CI now.

Part of https://github.com/flutter/flutter/issues/178827
- Adoption of flutter_lints in examples/api coming in a separate change
(cc @loic-sharma)

## Pre-launch Checklist

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
2025-11-26 01:10:39 +00:00

1596 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;
}
}
}