Add "flutter downgrade" command (#50506)
This commit is contained in:
parent
1f3d423f92
commit
e2554a9202
@ -25,6 +25,7 @@ import 'src/commands/create.dart';
|
||||
import 'src/commands/daemon.dart';
|
||||
import 'src/commands/devices.dart';
|
||||
import 'src/commands/doctor.dart';
|
||||
import 'src/commands/downgrade.dart';
|
||||
import 'src/commands/drive.dart';
|
||||
import 'src/commands/emulators.dart';
|
||||
import 'src/commands/format.dart';
|
||||
@ -76,6 +77,7 @@ Future<void> main(List<String> args) async {
|
||||
DaemonCommand(hidden: !verboseHelp),
|
||||
DevicesCommand(),
|
||||
DoctorCommand(verbose: verbose),
|
||||
DowngradeCommand(),
|
||||
DriveCommand(),
|
||||
EmulatorsCommand(),
|
||||
FormatCommand(),
|
||||
|
191
packages/flutter_tools/lib/src/commands/downgrade.dart
Normal file
191
packages/flutter_tools/lib/src/commands/downgrade.dart
Normal file
@ -0,0 +1,191 @@
|
||||
// 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 'package:process/process.dart';
|
||||
|
||||
import '../base/common.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/io.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../base/process.dart';
|
||||
import '../base/terminal.dart';
|
||||
import '../base/time.dart';
|
||||
import '../cache.dart';
|
||||
import '../globals.dart' as globals;
|
||||
import '../persistent_tool_state.dart';
|
||||
import '../runner/flutter_command.dart';
|
||||
import '../version.dart';
|
||||
|
||||
/// The flutter downgrade command returns the SDK to the last recorded version
|
||||
/// for a particular branch.
|
||||
///
|
||||
/// For example, suppose a user on the beta channel upgrades from 1.2.3 to 1.4.6.
|
||||
/// The tool will record that sha "abcdefg" was the last active beta channel in the
|
||||
/// persistent tool state. If the user is still on the beta channel and runs
|
||||
/// flutter downgrade, this will take the user back to "abcdefg". They will not be
|
||||
/// able to downgrade again, since the tool only records one prior version.
|
||||
/// Additionally, if they had switched channels to stable before trying to downgrade,
|
||||
/// the command would fail since there was no previously recorded stable version.
|
||||
class DowngradeCommand extends FlutterCommand {
|
||||
DowngradeCommand({
|
||||
PersistentToolState persistentToolState,
|
||||
Logger logger,
|
||||
ProcessManager processManager,
|
||||
FlutterVersion flutterVersion,
|
||||
AnsiTerminal terminal,
|
||||
Stdio stdio,
|
||||
FileSystem fileSystem,
|
||||
}) : _terminal = terminal,
|
||||
_flutterVersion = flutterVersion,
|
||||
_persistentToolState = persistentToolState,
|
||||
_processManager = processManager,
|
||||
_stdio = stdio,
|
||||
_logger = logger,
|
||||
_fileSystem = fileSystem {
|
||||
argParser.addOption(
|
||||
'working-directory',
|
||||
hide: true,
|
||||
help: 'Override the downgrade working directory for integration testing.'
|
||||
);
|
||||
argParser.addFlag(
|
||||
'prompt',
|
||||
defaultsTo: true,
|
||||
hide: true,
|
||||
help: 'Disable the downgrade prompt for integration testing.'
|
||||
);
|
||||
}
|
||||
|
||||
AnsiTerminal _terminal;
|
||||
FlutterVersion _flutterVersion;
|
||||
PersistentToolState _persistentToolState;
|
||||
ProcessUtils _processUtils;
|
||||
ProcessManager _processManager;
|
||||
Logger _logger;
|
||||
Stdio _stdio;
|
||||
FileSystem _fileSystem;
|
||||
|
||||
@override
|
||||
String get description => 'Downgrade Flutter to the last active version for the current channel.';
|
||||
|
||||
@override
|
||||
String get name => 'downgrade';
|
||||
|
||||
@override
|
||||
Future<FlutterCommandResult> runCommand() async {
|
||||
// Note: commands do not necessarily have access to the correct zone injected
|
||||
// values when being created. Fields must be lazily instantiated in runCommand,
|
||||
// at least until the zone injection is refactored.
|
||||
_terminal ??= globals.terminal;
|
||||
_logger ??= globals.logger;
|
||||
_flutterVersion ??= globals.flutterVersion;
|
||||
_persistentToolState ??= globals.persistentToolState;
|
||||
_processManager ??= globals.processManager;
|
||||
_processUtils ??= ProcessUtils(processManager: _processManager, logger: _logger);
|
||||
_stdio ??= globals.stdio;
|
||||
_fileSystem ??= globals.fs;
|
||||
String workingDirectory = Cache.flutterRoot;
|
||||
if (argResults.wasParsed('working-directory')) {
|
||||
workingDirectory = stringArg('working-directory');
|
||||
_flutterVersion = FlutterVersion(const SystemClock(), workingDirectory);
|
||||
}
|
||||
|
||||
final String currentChannel = _flutterVersion.channel;
|
||||
final Channel channel = getChannelForName(currentChannel);
|
||||
if (channel == null) {
|
||||
throwToolExit(
|
||||
'Flutter is not currently on a known channel. Use "flutter channel <name>" '
|
||||
'to switch to an official channel.',
|
||||
);
|
||||
}
|
||||
final String lastFlutterVesion = _persistentToolState.lastActiveVersion(channel);
|
||||
final String currentFlutterVersion = _flutterVersion.frameworkRevision;
|
||||
if (lastFlutterVesion == null || currentFlutterVersion == lastFlutterVesion) {
|
||||
final String trailing = await _createErrorMessage(workingDirectory, channel);
|
||||
throwToolExit(
|
||||
'There is no previously recorded version for channel "$currentChannel".\n'
|
||||
'$trailing'
|
||||
);
|
||||
}
|
||||
|
||||
// Detect unkown versions.
|
||||
final RunResult parseResult = await _processUtils.run(<String>[
|
||||
'git', 'describe', '--tags', lastFlutterVesion,
|
||||
], workingDirectory: workingDirectory);
|
||||
if (parseResult.exitCode != 0) {
|
||||
throwToolExit('Failed to parse version for downgrade:\n${parseResult.stderr}');
|
||||
}
|
||||
final String humanReadableVersion = parseResult.stdout;
|
||||
|
||||
// If there is a terminal attached, prompt the user to confirm the downgrade.
|
||||
if (_stdio.hasTerminal && boolArg('prompt')) {
|
||||
_terminal.usesTerminalUi = true;
|
||||
final String result = await _terminal.promptForCharInput(
|
||||
const <String>['y', 'n'],
|
||||
prompt: 'Downgrade flutter to version $humanReadableVersion?',
|
||||
logger: _logger,
|
||||
);
|
||||
if (result == 'n') {
|
||||
return FlutterCommandResult.success();
|
||||
}
|
||||
} else {
|
||||
_logger.printStatus('Downgrading Flutter to version $humanReadableVersion');
|
||||
}
|
||||
|
||||
// To downgrade the tool, we perform a git checkout --hard, and then
|
||||
// switch channels. The version recorded must have existed on that branch
|
||||
// so this operation is safe.
|
||||
try {
|
||||
await _processUtils.run(
|
||||
<String>['git', 'reset', '--hard', lastFlutterVesion],
|
||||
throwOnError: true,
|
||||
workingDirectory: workingDirectory,
|
||||
);
|
||||
} on ProcessException catch (error) {
|
||||
throwToolExit(
|
||||
'Unable to downgrade Flutter: The tool could not update to the version '
|
||||
'$humanReadableVersion. This may be due to git not being installed or an '
|
||||
'internal error. Please ensure that git is installed on your computer and '
|
||||
'retry again.\nError: $error.'
|
||||
);
|
||||
}
|
||||
try {
|
||||
await _processUtils.run(
|
||||
<String>['git', 'checkout', currentChannel, '--'],
|
||||
throwOnError: true,
|
||||
workingDirectory: workingDirectory,
|
||||
);
|
||||
} on ProcessException catch (error) {
|
||||
throwToolExit(
|
||||
'Unable to downgrade Flutter: The tool could not switch to the channel '
|
||||
'$currentChannel. This may be due to git not being installed or an '
|
||||
'internal error. Please ensure that git is installed on your computer '
|
||||
'and retry again.\nError: $error.'
|
||||
);
|
||||
}
|
||||
await FlutterVersion.resetFlutterVersionFreshnessCheck();
|
||||
_logger.printStatus('Success');
|
||||
return FlutterCommandResult.success();
|
||||
}
|
||||
|
||||
// Formats an error message that lists the currently stored versions.
|
||||
Future<String> _createErrorMessage(String workingDirectory, Channel currentChannel) async {
|
||||
final StringBuffer buffer = StringBuffer();
|
||||
for (final Channel channel in Channel.values) {
|
||||
if (channel == currentChannel) {
|
||||
continue;
|
||||
}
|
||||
final String sha = _persistentToolState.lastActiveVersion(channel);
|
||||
if (sha == null) {
|
||||
continue;
|
||||
}
|
||||
final RunResult parseResult = await _processUtils.run(<String>[
|
||||
'git', 'describe', '--tags', sha,
|
||||
], workingDirectory: workingDirectory);
|
||||
if (parseResult.exitCode == 0) {
|
||||
buffer.writeln('Channel "${getNameForChannel(channel)}" was previously on: ${parseResult.stdout}.');
|
||||
}
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import '../base/common.dart';
|
||||
import '../base/io.dart';
|
||||
import '../base/os.dart';
|
||||
import '../base/process.dart';
|
||||
import '../base/time.dart';
|
||||
import '../cache.dart';
|
||||
import '../dart/pub.dart';
|
||||
import '../globals.dart' as globals;
|
||||
@ -34,6 +35,11 @@ class UpgradeCommand extends FlutterCommand {
|
||||
help: 'For the second half of the upgrade flow requiring the new '
|
||||
'version of Flutter. Should not be invoked manually, but '
|
||||
're-entrantly by the standard upgrade command.',
|
||||
)
|
||||
..addOption(
|
||||
'working-directory',
|
||||
hide: true,
|
||||
help: 'Override the upgrade working directoy for integration testing.'
|
||||
);
|
||||
}
|
||||
|
||||
@ -50,36 +56,50 @@ class UpgradeCommand extends FlutterCommand {
|
||||
|
||||
@override
|
||||
Future<FlutterCommandResult> runCommand() {
|
||||
_commandRunner.workingDirectory = stringArg('working-directory') ?? Cache.flutterRoot;
|
||||
return _commandRunner.runCommand(
|
||||
boolArg('force'),
|
||||
boolArg('continue'),
|
||||
GitTagVersion.determine(),
|
||||
globals.flutterVersion,
|
||||
force: boolArg('force'),
|
||||
continueFlow: boolArg('continue'),
|
||||
testFlow: stringArg('working-directory') != null,
|
||||
gitTagVersion: GitTagVersion.determine(processUtils),
|
||||
flutterVersion: stringArg('working-directory') == null
|
||||
? globals.flutterVersion
|
||||
: FlutterVersion(const SystemClock(), _commandRunner.workingDirectory),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
class UpgradeCommandRunner {
|
||||
Future<FlutterCommandResult> runCommand(
|
||||
bool force,
|
||||
bool continueFlow,
|
||||
GitTagVersion gitTagVersion,
|
||||
FlutterVersion flutterVersion,
|
||||
) async {
|
||||
|
||||
String workingDirectory;
|
||||
|
||||
Future<FlutterCommandResult> runCommand({
|
||||
@required bool force,
|
||||
@required bool continueFlow,
|
||||
@required bool testFlow,
|
||||
@required GitTagVersion gitTagVersion,
|
||||
@required FlutterVersion flutterVersion,
|
||||
}) async {
|
||||
if (!continueFlow) {
|
||||
await runCommandFirstHalf(force, gitTagVersion, flutterVersion);
|
||||
await runCommandFirstHalf(
|
||||
force: force,
|
||||
gitTagVersion: gitTagVersion,
|
||||
flutterVersion: flutterVersion,
|
||||
testFlow: testFlow,
|
||||
);
|
||||
} else {
|
||||
await runCommandSecondHalf(flutterVersion);
|
||||
}
|
||||
return FlutterCommandResult.success();
|
||||
}
|
||||
|
||||
Future<void> runCommandFirstHalf(
|
||||
bool force,
|
||||
GitTagVersion gitTagVersion,
|
||||
FlutterVersion flutterVersion,
|
||||
) async {
|
||||
Future<void> runCommandFirstHalf({
|
||||
@required bool force,
|
||||
@required GitTagVersion gitTagVersion,
|
||||
@required FlutterVersion flutterVersion,
|
||||
@required bool testFlow,
|
||||
}) async {
|
||||
await verifyUpstreamConfigured();
|
||||
if (!force && gitTagVersion == const GitTagVersion.unknown()) {
|
||||
// If the commit is a recognized branch and not master,
|
||||
@ -110,6 +130,7 @@ class UpgradeCommandRunner {
|
||||
'command with --force.'
|
||||
);
|
||||
}
|
||||
recordState(flutterVersion);
|
||||
await resetChanges(gitTagVersion);
|
||||
await upgradeChannel(flutterVersion);
|
||||
final bool alreadyUpToDate = await attemptFastForward(flutterVersion);
|
||||
@ -117,11 +138,19 @@ class UpgradeCommandRunner {
|
||||
// If the upgrade was a no op, then do not continue with the second half.
|
||||
globals.printStatus('Flutter is already up to date on channel ${flutterVersion.channel}');
|
||||
globals.printStatus('$flutterVersion');
|
||||
} else {
|
||||
} else if (!testFlow) {
|
||||
await flutterUpgradeContinue();
|
||||
}
|
||||
}
|
||||
|
||||
void recordState(FlutterVersion flutterVersion) {
|
||||
final Channel channel = getChannelForName(flutterVersion.channel);
|
||||
if (channel == null) {
|
||||
return;
|
||||
}
|
||||
globals.persistentToolState.updateLastActiveVersion(flutterVersion.frameworkRevision, channel);
|
||||
}
|
||||
|
||||
Future<void> flutterUpgradeContinue() async {
|
||||
final int code = await processUtils.stream(
|
||||
<String>[
|
||||
@ -130,7 +159,7 @@ class UpgradeCommandRunner {
|
||||
'--continue',
|
||||
'--no-version-check',
|
||||
],
|
||||
workingDirectory: Cache.flutterRoot,
|
||||
workingDirectory: workingDirectory,
|
||||
allowReentrantFlutter: true,
|
||||
environment: Map<String, String>.of(globals.platform.environment),
|
||||
);
|
||||
@ -156,7 +185,7 @@ class UpgradeCommandRunner {
|
||||
final RunResult result = await processUtils.run(
|
||||
<String>['git', 'status', '-s'],
|
||||
throwOnError: true,
|
||||
workingDirectory: Cache.flutterRoot,
|
||||
workingDirectory: workingDirectory,
|
||||
);
|
||||
return result.stdout.trim().isNotEmpty;
|
||||
} on ProcessException catch (error) {
|
||||
@ -179,13 +208,13 @@ class UpgradeCommandRunner {
|
||||
await processUtils.run(
|
||||
<String>[ 'git', 'rev-parse', '@{u}'],
|
||||
throwOnError: true,
|
||||
workingDirectory: Cache.flutterRoot,
|
||||
workingDirectory: workingDirectory,
|
||||
);
|
||||
} catch (e) {
|
||||
throwToolExit(
|
||||
'Unable to upgrade Flutter: no origin repository configured. '
|
||||
"Run 'git remote add origin "
|
||||
"https://github.com/flutter/flutter' in ${Cache.flutterRoot}",
|
||||
"https://github.com/flutter/flutter' in $workingDirectory",
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -206,7 +235,7 @@ class UpgradeCommandRunner {
|
||||
await processUtils.run(
|
||||
<String>['git', 'reset', '--hard', tag],
|
||||
throwOnError: true,
|
||||
workingDirectory: Cache.flutterRoot,
|
||||
workingDirectory: workingDirectory,
|
||||
);
|
||||
} on ProcessException catch (error) {
|
||||
throwToolExit(
|
||||
@ -223,7 +252,7 @@ class UpgradeCommandRunner {
|
||||
/// If the user is on a deprecated channel, attempts to migrate them off of
|
||||
/// it.
|
||||
Future<void> upgradeChannel(FlutterVersion flutterVersion) async {
|
||||
globals.printStatus('Upgrading Flutter from ${Cache.flutterRoot}...');
|
||||
globals.printStatus('Upgrading Flutter from $workingDirectory...');
|
||||
await ChannelCommand.upgradeChannel();
|
||||
}
|
||||
|
||||
@ -237,7 +266,7 @@ class UpgradeCommandRunner {
|
||||
Future<bool> attemptFastForward(FlutterVersion oldFlutterVersion) async {
|
||||
final int code = await processUtils.stream(
|
||||
<String>['git', 'pull', '--ff'],
|
||||
workingDirectory: Cache.flutterRoot,
|
||||
workingDirectory: workingDirectory,
|
||||
mapFunction: (String line) => matchesGitLine(line) ? null : line,
|
||||
);
|
||||
if (code != 0) {
|
||||
@ -247,7 +276,7 @@ class UpgradeCommandRunner {
|
||||
// Check if the upgrade did anything.
|
||||
bool alreadyUpToDate = false;
|
||||
try {
|
||||
final FlutterVersion newFlutterVersion = FlutterVersion();
|
||||
final FlutterVersion newFlutterVersion = FlutterVersion(const SystemClock(), workingDirectory);
|
||||
alreadyUpToDate = newFlutterVersion.channel == oldFlutterVersion.channel &&
|
||||
newFlutterVersion.frameworkRevision == oldFlutterVersion.frameworkRevision;
|
||||
} catch (e) {
|
||||
@ -268,7 +297,7 @@ class UpgradeCommandRunner {
|
||||
<String>[
|
||||
globals.fs.path.join('bin', 'flutter'), '--no-color', '--no-version-check', 'precache',
|
||||
],
|
||||
workingDirectory: Cache.flutterRoot,
|
||||
workingDirectory: workingDirectory,
|
||||
allowReentrantFlutter: true,
|
||||
environment: Map<String, String>.of(globals.platform.environment),
|
||||
);
|
||||
@ -296,7 +325,7 @@ class UpgradeCommandRunner {
|
||||
<String>[
|
||||
globals.fs.path.join('bin', 'flutter'), '--no-version-check', 'doctor',
|
||||
],
|
||||
workingDirectory: Cache.flutterRoot,
|
||||
workingDirectory: workingDirectory,
|
||||
allowReentrantFlutter: true,
|
||||
);
|
||||
}
|
||||
|
@ -57,8 +57,29 @@ class VersionCommand extends FlutterCommand {
|
||||
final List<String> tags = await getTags();
|
||||
if (argResults.rest.isEmpty) {
|
||||
tags.forEach(globals.printStatus);
|
||||
return const FlutterCommandResult(ExitStatus.success);
|
||||
return FlutterCommandResult.success();
|
||||
}
|
||||
|
||||
globals.printStatus(
|
||||
'╔══════════════════════════════════════════════════════════════════════════════╗\n'
|
||||
'║ Warning: "flutter version" will leave the SDK in a detached HEAD state. ║\n'
|
||||
'║ If you are using the command to return to a previously installed SDK version ║\n'
|
||||
'║ consider using the "flutter downgrade" command instead. ║\n'
|
||||
'╚══════════════════════════════════════════════════════════════════════════════╝\n',
|
||||
emphasis: true,
|
||||
);
|
||||
if (globals.stdio.stdinHasTerminal) {
|
||||
globals.terminal.usesTerminalUi = true;
|
||||
final String result = await globals.terminal.promptForCharInput(
|
||||
<String>['y', 'n'],
|
||||
logger: globals.logger,
|
||||
prompt: 'Are you sure you want to proceed?'
|
||||
);
|
||||
if (result == 'n') {
|
||||
return FlutterCommandResult.success();
|
||||
}
|
||||
}
|
||||
|
||||
final String version = argResults.rest[0].replaceFirst('v', '');
|
||||
if (!tags.contains('v$version')) {
|
||||
globals.printError('There is no version: $version');
|
||||
|
@ -9,6 +9,7 @@ import 'base/config.dart';
|
||||
import 'base/context.dart';
|
||||
import 'base/file_system.dart';
|
||||
import 'base/logger.dart';
|
||||
import 'version.dart';
|
||||
|
||||
/// A class that represents global (non-project-specific) internal state that
|
||||
/// must persist across tool invocations.
|
||||
@ -37,6 +38,14 @@ abstract class PersistentToolState {
|
||||
///
|
||||
/// May give null if the value has not been set.
|
||||
bool redisplayWelcomeMessage;
|
||||
|
||||
/// Returns the last active version for a given [channel].
|
||||
///
|
||||
/// If there was no active prior version, returns `null` instead.
|
||||
String lastActiveVersion(Channel channel);
|
||||
|
||||
/// Update the last active version for a given [channel].
|
||||
void updateLastActiveVersion(String fullGitHash, Channel channel);
|
||||
}
|
||||
|
||||
class _DefaultPersistentToolState implements PersistentToolState {
|
||||
@ -63,6 +72,12 @@ class _DefaultPersistentToolState implements PersistentToolState {
|
||||
|
||||
static const String _kFileName = '.flutter_tool_state';
|
||||
static const String _kRedisplayWelcomeMessage = 'redisplay-welcome-message';
|
||||
static const Map<Channel, String> _lastActiveVersionKeys = <Channel,String>{
|
||||
Channel.master: 'last-active-master-version',
|
||||
Channel.dev: 'last-active-dev-version',
|
||||
Channel.beta: 'last-active-beta-version',
|
||||
Channel.stable: 'last-active-stable-version'
|
||||
};
|
||||
|
||||
final Config _config;
|
||||
|
||||
@ -71,8 +86,26 @@ class _DefaultPersistentToolState implements PersistentToolState {
|
||||
return _config.getValue(_kRedisplayWelcomeMessage) as bool;
|
||||
}
|
||||
|
||||
@override
|
||||
String lastActiveVersion(Channel channel) {
|
||||
final String versionKey = _versionKeyFor(channel);
|
||||
assert(versionKey != null);
|
||||
return _config.getValue(versionKey) as String;
|
||||
}
|
||||
|
||||
@override
|
||||
set redisplayWelcomeMessage(bool value) {
|
||||
_config.setValue(_kRedisplayWelcomeMessage, value);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateLastActiveVersion(String fullGitHash, Channel channel) {
|
||||
final String versionKey = _versionKeyFor(channel);
|
||||
assert(versionKey != null);
|
||||
_config.setValue(versionKey, fullGitHash);
|
||||
}
|
||||
|
||||
String _versionKeyFor(Channel channel) {
|
||||
return _lastActiveVersionKeys[channel];
|
||||
}
|
||||
}
|
||||
|
@ -15,14 +15,45 @@ import 'cache.dart';
|
||||
import 'convert.dart';
|
||||
import 'globals.dart' as globals;
|
||||
|
||||
/// The names of each channel/branch in order of increasing stability.
|
||||
enum Channel {
|
||||
master,
|
||||
dev,
|
||||
beta,
|
||||
stable,
|
||||
}
|
||||
|
||||
/// Retrieve a human-readable name for a given [channel].
|
||||
///
|
||||
/// Requires [FlutterVersion.officialChannels] to be correctly ordered.
|
||||
String getNameForChannel(Channel channel) {
|
||||
return FlutterVersion.officialChannels.elementAt(channel.index);
|
||||
}
|
||||
|
||||
/// Retrieve the [Channel] representation for a string [name].
|
||||
///
|
||||
/// Returns `null` if [name] is not in the list of official channels, according
|
||||
/// to [FlutterVersion.officialChannels].
|
||||
Channel getChannelForName(String name) {
|
||||
if (FlutterVersion.officialChannels.contains(name)) {
|
||||
return Channel.values[FlutterVersion.officialChannels.toList().indexOf(name)];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class FlutterVersion {
|
||||
FlutterVersion([this._clock = const SystemClock()]) {
|
||||
_frameworkRevision = _runGit(gitLog(<String>['-n', '1', '--pretty=format:%H']).join(' '));
|
||||
_gitTagVersion = GitTagVersion.determine();
|
||||
FlutterVersion([this._clock = const SystemClock(), this._workingDirectory]) {
|
||||
_frameworkRevision = _runGit(
|
||||
gitLog(<String>['-n', '1', '--pretty=format:%H']).join(' '),
|
||||
processUtils,
|
||||
_workingDirectory,
|
||||
);
|
||||
_gitTagVersion = GitTagVersion.determine(processUtils, _workingDirectory);
|
||||
_frameworkVersion = gitTagVersion.frameworkVersionFor(_frameworkRevision);
|
||||
}
|
||||
|
||||
final SystemClock _clock;
|
||||
final String _workingDirectory;
|
||||
|
||||
String _repositoryUrl;
|
||||
String get repositoryUrl {
|
||||
@ -60,11 +91,19 @@ class FlutterVersion {
|
||||
/// `master`, `dev`, `beta`, `stable`; or old ones, like `alpha`, `hackathon`, ...
|
||||
String get channel {
|
||||
if (_channel == null) {
|
||||
final String channel = _runGit('git rev-parse --abbrev-ref --symbolic @{u}');
|
||||
final String channel = _runGit(
|
||||
'git rev-parse --abbrev-ref --symbolic @{u}',
|
||||
processUtils,
|
||||
_workingDirectory,
|
||||
);
|
||||
final int slash = channel.indexOf('/');
|
||||
if (slash != -1) {
|
||||
final String remote = channel.substring(0, slash);
|
||||
_repositoryUrl = _runGit('git ls-remote --get-url $remote');
|
||||
_repositoryUrl = _runGit(
|
||||
'git ls-remote --get-url $remote',
|
||||
processUtils,
|
||||
_workingDirectory,
|
||||
);
|
||||
_channel = channel.substring(slash + 1);
|
||||
} else if (channel.isEmpty) {
|
||||
_channel = 'unknown';
|
||||
@ -88,7 +127,11 @@ class FlutterVersion {
|
||||
|
||||
String _frameworkAge;
|
||||
String get frameworkAge {
|
||||
return _frameworkAge ??= _runGit(gitLog(<String>['-n', '1', '--pretty=format:%ar']).join(' '));
|
||||
return _frameworkAge ??= _runGit(
|
||||
gitLog(<String>['-n', '1', '--pretty=format:%ar']).join(' '),
|
||||
processUtils,
|
||||
_workingDirectory,
|
||||
);
|
||||
}
|
||||
|
||||
String _frameworkVersion;
|
||||
@ -226,7 +269,7 @@ class FlutterVersion {
|
||||
/// the branch name will be returned as `'[user-branch]'`.
|
||||
String getBranchName({ bool redactUnknownBranches = false }) {
|
||||
_branch ??= () {
|
||||
final String branch = _runGit('git rev-parse --abbrev-ref HEAD');
|
||||
final String branch = _runGit('git rev-parse --abbrev-ref HEAD', processUtils);
|
||||
return branch == 'HEAD' ? channel : branch;
|
||||
}();
|
||||
if (redactUnknownBranches || _branch.isEmpty) {
|
||||
@ -599,10 +642,10 @@ String _runSync(List<String> command, { bool lenient = true }) {
|
||||
return '';
|
||||
}
|
||||
|
||||
String _runGit(String command) {
|
||||
String _runGit(String command, ProcessUtils processUtils, [String workingDirectory]) {
|
||||
return processUtils.runSync(
|
||||
command.split(' '),
|
||||
workingDirectory: Cache.flutterRoot,
|
||||
workingDirectory: workingDirectory ?? Cache.flutterRoot,
|
||||
).stdout.trim();
|
||||
}
|
||||
|
||||
@ -658,8 +701,8 @@ class GitTagVersion {
|
||||
/// The git hash (or an abbreviation thereof) for this commit.
|
||||
final String hash;
|
||||
|
||||
static GitTagVersion determine() {
|
||||
return parse(_runGit('git describe --match v*.*.* --first-parent --long --tags'));
|
||||
static GitTagVersion determine(ProcessUtils processUtils, [String workingDirectory]) {
|
||||
return parse(_runGit('git describe --match v*.*.* --first-parent --long --tags', processUtils, workingDirectory));
|
||||
}
|
||||
|
||||
static GitTagVersion parse(String version) {
|
||||
|
@ -0,0 +1,249 @@
|
||||
// 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 'package:file/file.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/base/io.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/base/terminal.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
import 'package:flutter_tools/src/commands/downgrade.dart';
|
||||
import 'package:flutter_tools/src/persistent_tool_state.dart';
|
||||
import 'package:flutter_tools/src/version.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
import '../../src/mocks.dart';
|
||||
|
||||
void main() {
|
||||
FileSystem fileSystem;
|
||||
BufferLogger bufferLogger;
|
||||
AnsiTerminal terminal;
|
||||
ProcessManager processManager;
|
||||
MockStdio mockStdio;
|
||||
FlutterVersion flutterVersion;
|
||||
|
||||
setUpAll(() {
|
||||
Cache.disableLocking();
|
||||
});
|
||||
|
||||
tearDownAll(() {
|
||||
Cache.enableLocking();
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
flutterVersion = MockFlutterVersion();
|
||||
mockStdio = MockStdio();
|
||||
processManager = FakeProcessManager.any();
|
||||
terminal = MockTerminal();
|
||||
fileSystem = MemoryFileSystem.test();
|
||||
bufferLogger = BufferLogger(terminal: terminal, outputPreferences: OutputPreferences.test());
|
||||
});
|
||||
|
||||
testUsingContext('Downgrade exits on unknown channel', () async {
|
||||
fileSystem.currentDirectory.childFile('.flutter_tool_state')
|
||||
.writeAsStringSync('{"last-active-master-version":"invalid"}');
|
||||
final DowngradeCommand command = DowngradeCommand(
|
||||
persistentToolState: PersistentToolState.test(directory: fileSystem.currentDirectory, logger: bufferLogger),
|
||||
processManager: processManager,
|
||||
terminal: terminal,
|
||||
stdio: mockStdio,
|
||||
flutterVersion: flutterVersion,
|
||||
logger: bufferLogger,
|
||||
);
|
||||
applyMocksToCommand(command);
|
||||
|
||||
expect(createTestCommandRunner(command).run(const <String>['downgrade']),
|
||||
throwsToolExit(message: 'Flutter is not currently on a known channel.'));
|
||||
});
|
||||
|
||||
testUsingContext('Downgrade exits on no recorded version', () async {
|
||||
when(flutterVersion.channel).thenReturn('dev');
|
||||
fileSystem.currentDirectory.childFile('.flutter_tool_state')
|
||||
.writeAsStringSync('{"last-active-master-version":"abcd"}');
|
||||
final DowngradeCommand command = DowngradeCommand(
|
||||
persistentToolState: PersistentToolState.test(directory: fileSystem.currentDirectory, logger: bufferLogger),
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>[
|
||||
'git', 'describe', '--tags', 'abcd'
|
||||
],
|
||||
exitCode: 0,
|
||||
stdout: 'v1.2.3'
|
||||
)
|
||||
]),
|
||||
terminal: terminal,
|
||||
stdio: mockStdio,
|
||||
flutterVersion: flutterVersion,
|
||||
logger: bufferLogger,
|
||||
);
|
||||
applyMocksToCommand(command);
|
||||
|
||||
expect(createTestCommandRunner(command).run(const <String>['downgrade']),
|
||||
throwsToolExit(message:
|
||||
'There is no previously recorded version for channel "dev".\n'
|
||||
'Channel "master" was previously on: v1.2.3.'
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testUsingContext('Downgrade exits on unknown recorded version', () async {
|
||||
when(flutterVersion.channel).thenReturn('master');
|
||||
fileSystem.currentDirectory.childFile('.flutter_tool_state')
|
||||
.writeAsStringSync('{"last-active-master-version":"invalid"}');
|
||||
final DowngradeCommand command = DowngradeCommand(
|
||||
persistentToolState: PersistentToolState.test(directory: fileSystem.currentDirectory, logger: bufferLogger),
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>[
|
||||
'git', 'describe', '--tags', 'invalid'
|
||||
],
|
||||
exitCode: 1,
|
||||
)
|
||||
]),
|
||||
terminal: terminal,
|
||||
stdio: mockStdio,
|
||||
flutterVersion: flutterVersion,
|
||||
logger: bufferLogger,
|
||||
);
|
||||
applyMocksToCommand(command);
|
||||
|
||||
expect(createTestCommandRunner(command).run(const <String>['downgrade']),
|
||||
throwsToolExit(message: 'Failed to parse version for downgrade'));
|
||||
});
|
||||
|
||||
testUsingContext('Downgrade prompts for user input when terminal is attached - y', () async {
|
||||
when(flutterVersion.channel).thenReturn('master');
|
||||
when(mockStdio.hasTerminal).thenReturn(true);
|
||||
fileSystem.currentDirectory.childFile('.flutter_tool_state')
|
||||
.writeAsStringSync('{"last-active-master-version":"g6b00b5e88"}');
|
||||
final DowngradeCommand command = DowngradeCommand(
|
||||
persistentToolState: PersistentToolState.test(directory: fileSystem.currentDirectory, logger: bufferLogger),
|
||||
processManager: processManager,
|
||||
terminal: terminal,
|
||||
stdio: mockStdio,
|
||||
flutterVersion: flutterVersion,
|
||||
logger: bufferLogger,
|
||||
);
|
||||
applyMocksToCommand(command);
|
||||
|
||||
when(terminal.promptForCharInput(
|
||||
const <String>['y', 'n'],
|
||||
prompt: anyNamed('prompt'),
|
||||
logger: anyNamed('logger'),
|
||||
)).thenAnswer((Invocation invocation) async => 'y');
|
||||
|
||||
await createTestCommandRunner(command).run(const <String>['downgrade']);
|
||||
|
||||
verify(terminal.promptForCharInput(
|
||||
const <String>['y', 'n'],
|
||||
prompt: anyNamed('prompt'),
|
||||
logger: anyNamed('logger'),
|
||||
)).called(1);
|
||||
expect(bufferLogger.statusText, contains('Success'));
|
||||
});
|
||||
|
||||
testUsingContext('Downgrade prompts for user input when terminal is attached - n', () async {
|
||||
when(flutterVersion.channel).thenReturn('master');
|
||||
when(mockStdio.hasTerminal).thenReturn(true);
|
||||
fileSystem.currentDirectory.childFile('.flutter_tool_state')
|
||||
.writeAsStringSync('{"last-active-master-version":"g6b00b5e88"}');
|
||||
final DowngradeCommand command = DowngradeCommand(
|
||||
persistentToolState: PersistentToolState.test(directory: fileSystem.currentDirectory, logger: bufferLogger),
|
||||
processManager: processManager,
|
||||
terminal: terminal,
|
||||
stdio: mockStdio,
|
||||
flutterVersion: flutterVersion,
|
||||
logger: bufferLogger,
|
||||
);
|
||||
applyMocksToCommand(command);
|
||||
|
||||
when(terminal.promptForCharInput(
|
||||
const <String>['y', 'n'],
|
||||
prompt: anyNamed('prompt'),
|
||||
logger: anyNamed('logger'),
|
||||
)).thenAnswer((Invocation invocation) async => 'n');
|
||||
|
||||
await createTestCommandRunner(command).run(const <String>['downgrade']);
|
||||
|
||||
verify(terminal.promptForCharInput(
|
||||
const <String>['y', 'n'],
|
||||
prompt: anyNamed('prompt'),
|
||||
logger: anyNamed('logger'),
|
||||
)).called(1);
|
||||
expect(bufferLogger.statusText, isNot(contains('Success')));
|
||||
});
|
||||
|
||||
testUsingContext('Downgrade does not prompt when there is no terminal', () async {
|
||||
when(flutterVersion.channel).thenReturn('master');
|
||||
when(mockStdio.hasTerminal).thenReturn(false);
|
||||
fileSystem.currentDirectory.childFile('.flutter_tool_state')
|
||||
.writeAsStringSync('{"last-active-master-version":"g6b00b5e88"}');
|
||||
final DowngradeCommand command = DowngradeCommand(
|
||||
persistentToolState: PersistentToolState.test(
|
||||
directory: fileSystem.currentDirectory,
|
||||
logger: bufferLogger,
|
||||
),
|
||||
processManager: processManager,
|
||||
terminal: terminal,
|
||||
stdio: mockStdio,
|
||||
flutterVersion: flutterVersion,
|
||||
logger: bufferLogger,
|
||||
);
|
||||
applyMocksToCommand(command);
|
||||
|
||||
await createTestCommandRunner(command).run(const <String>['downgrade']);
|
||||
|
||||
verifyNever(terminal.promptForCharInput(
|
||||
const <String>['y', 'n'],
|
||||
prompt: anyNamed('prompt'),
|
||||
logger: anyNamed('logger'),
|
||||
));
|
||||
expect(bufferLogger.statusText, contains('Success'));
|
||||
});
|
||||
|
||||
testUsingContext('Downgrade performs correct git commands', () async {
|
||||
when(flutterVersion.channel).thenReturn('master');
|
||||
when(mockStdio.hasTerminal).thenReturn(false);
|
||||
fileSystem.currentDirectory.childFile('.flutter_tool_state')
|
||||
.writeAsStringSync('{"last-active-master-version":"g6b00b5e88"}');
|
||||
final DowngradeCommand command = DowngradeCommand(
|
||||
persistentToolState: PersistentToolState.test(
|
||||
directory: fileSystem.currentDirectory,
|
||||
logger: bufferLogger,
|
||||
),
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>[
|
||||
'git', 'describe', '--tags', 'g6b00b5e88'
|
||||
],
|
||||
stdout: 'v1.2.3',
|
||||
),
|
||||
const FakeCommand(
|
||||
command: <String>[
|
||||
'git', 'reset', '--hard', 'g6b00b5e88'
|
||||
],
|
||||
),
|
||||
const FakeCommand(
|
||||
command: <String>[
|
||||
'git', 'checkout', 'master', '--'
|
||||
]
|
||||
),
|
||||
]),
|
||||
terminal: terminal,
|
||||
stdio: mockStdio,
|
||||
flutterVersion: flutterVersion,
|
||||
logger: bufferLogger,
|
||||
);
|
||||
applyMocksToCommand(command);
|
||||
|
||||
await createTestCommandRunner(command).run(const <String>['downgrade']);
|
||||
|
||||
expect(bufferLogger.statusText, contains('Success'));
|
||||
});
|
||||
}
|
||||
|
||||
class MockTerminal extends Mock implements AnsiTerminal {}
|
||||
class MockStdio extends Mock implements Stdio {}
|
@ -8,11 +8,13 @@ import 'dart:io';
|
||||
|
||||
import 'package:flutter_tools/src/base/common.dart';
|
||||
import 'package:flutter_tools/src/base/io.dart';
|
||||
import 'package:flutter_tools/src/base/terminal.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
import 'package:flutter_tools/src/commands/version.dart';
|
||||
import 'package:flutter_tools/src/version.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:process/process.dart';
|
||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
@ -20,10 +22,18 @@ import '../../src/mocks.dart' show MockProcess;
|
||||
|
||||
void main() {
|
||||
group('version', () {
|
||||
MockStdio mockStdio;
|
||||
|
||||
setUpAll(() {
|
||||
Cache.disableLocking();
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
mockStdio = MockStdio();
|
||||
when(mockStdio.stdinHasTerminal).thenReturn(false);
|
||||
when(mockStdio.hasTerminal).thenReturn(false);
|
||||
});
|
||||
|
||||
testUsingContext('version ls', () async {
|
||||
final VersionCommand command = VersionCommand();
|
||||
await createTestCommandRunner(command).run(<String>[
|
||||
@ -33,11 +43,18 @@ void main() {
|
||||
expect(testLogger.statusText, equals('v10.0.0\r\nv20.0.0\n'));
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => MockProcessManager(),
|
||||
Stdio: () => mockStdio,
|
||||
});
|
||||
|
||||
testUsingContext('version switch', () async {
|
||||
testUsingContext('version switch prompt is accepted', () async {
|
||||
when(mockStdio.stdinHasTerminal).thenReturn(true);
|
||||
const String version = '10.0.0';
|
||||
final VersionCommand command = VersionCommand();
|
||||
when(globals.terminal.promptForCharInput(<String>['y', 'n'],
|
||||
logger: anyNamed('logger'),
|
||||
prompt: 'Are you sure you want to proceed?')
|
||||
).thenAnswer((Invocation invocation) async => 'y');
|
||||
|
||||
await createTestCommandRunner(command).run(<String>[
|
||||
'version',
|
||||
'--no-pub',
|
||||
@ -46,6 +63,29 @@ void main() {
|
||||
expect(testLogger.statusText, contains('Switching Flutter to version $version'));
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => MockProcessManager(),
|
||||
Stdio: () => mockStdio,
|
||||
AnsiTerminal: () => MockTerminal(),
|
||||
});
|
||||
|
||||
testUsingContext('version switch prompt is declined', () async {
|
||||
when(mockStdio.stdinHasTerminal).thenReturn(true);
|
||||
const String version = '10.0.0';
|
||||
final VersionCommand command = VersionCommand();
|
||||
when(globals.terminal.promptForCharInput(<String>['y', 'n'],
|
||||
logger: anyNamed('logger'),
|
||||
prompt: 'Are you sure you want to proceed?')
|
||||
).thenAnswer((Invocation invocation) async => 'n');
|
||||
|
||||
await createTestCommandRunner(command).run(<String>[
|
||||
'version',
|
||||
'--no-pub',
|
||||
version,
|
||||
]);
|
||||
expect(testLogger.statusText, isNot(contains('Switching Flutter to version $version')));
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => MockProcessManager(),
|
||||
Stdio: () => mockStdio,
|
||||
AnsiTerminal: () => MockTerminal(),
|
||||
});
|
||||
|
||||
testUsingContext('version switch, latest commit query fails', () async {
|
||||
@ -59,6 +99,7 @@ void main() {
|
||||
expect(testLogger.errorText, contains('git failed'));
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => MockProcessManager(latestCommitFails: true),
|
||||
Stdio: () => mockStdio,
|
||||
});
|
||||
|
||||
testUsingContext('latest commit is parsable when query fails', () {
|
||||
@ -69,6 +110,7 @@ void main() {
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => MockProcessManager(latestCommitFails: true),
|
||||
Stdio: () => mockStdio,
|
||||
});
|
||||
|
||||
testUsingContext('switch to not supported version without force', () async {
|
||||
@ -82,6 +124,7 @@ void main() {
|
||||
expect(testLogger.errorText, contains('Version command is not supported in'));
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => MockProcessManager(),
|
||||
Stdio: () => mockStdio,
|
||||
});
|
||||
|
||||
testUsingContext('switch to not supported version with force', () async {
|
||||
@ -96,6 +139,7 @@ void main() {
|
||||
expect(testLogger.statusText, contains('Switching Flutter to version $version with force'));
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => MockProcessManager(),
|
||||
Stdio: () => mockStdio,
|
||||
});
|
||||
|
||||
testUsingContext('tool exit on confusing version', () async {
|
||||
@ -111,6 +155,7 @@ void main() {
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => MockProcessManager(),
|
||||
Stdio: () => mockStdio,
|
||||
});
|
||||
|
||||
testUsingContext("exit tool if can't get the tags", () async {
|
||||
@ -123,10 +168,13 @@ void main() {
|
||||
}
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => MockProcessManager(failGitTag: true),
|
||||
Stdio: () => mockStdio,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class MockTerminal extends Mock implements AnsiTerminal {}
|
||||
class MockStdio extends Mock implements Stdio {}
|
||||
class MockProcessManager extends Mock implements ProcessManager {
|
||||
MockProcessManager({
|
||||
this.failGitTag = false,
|
||||
|
@ -57,10 +57,11 @@ void main() {
|
||||
|
||||
testUsingContext('throws on unknown tag, official branch, noforce', () async {
|
||||
final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
|
||||
false,
|
||||
false,
|
||||
const GitTagVersion.unknown(),
|
||||
flutterVersion,
|
||||
force: false,
|
||||
continueFlow: false,
|
||||
testFlow: false,
|
||||
gitTagVersion: const GitTagVersion.unknown(),
|
||||
flutterVersion: flutterVersion,
|
||||
);
|
||||
expect(result, throwsToolExit());
|
||||
}, overrides: <Type, Generator>{
|
||||
@ -69,10 +70,11 @@ void main() {
|
||||
|
||||
testUsingContext('does not throw on unknown tag, official branch, force', () async {
|
||||
final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
|
||||
true,
|
||||
false,
|
||||
const GitTagVersion.unknown(),
|
||||
flutterVersion,
|
||||
force: true,
|
||||
continueFlow: false,
|
||||
testFlow: false,
|
||||
gitTagVersion: const GitTagVersion.unknown(),
|
||||
flutterVersion: flutterVersion,
|
||||
);
|
||||
expect(await result, FlutterCommandResult.success());
|
||||
}, overrides: <Type, Generator>{
|
||||
@ -83,10 +85,11 @@ void main() {
|
||||
testUsingContext('throws tool exit with uncommitted changes', () async {
|
||||
fakeCommandRunner.willHaveUncomittedChanges = true;
|
||||
final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
|
||||
false,
|
||||
false,
|
||||
gitTagVersion,
|
||||
flutterVersion,
|
||||
force: false,
|
||||
continueFlow: false,
|
||||
testFlow: false,
|
||||
gitTagVersion: gitTagVersion,
|
||||
flutterVersion: flutterVersion,
|
||||
);
|
||||
expect(result, throwsToolExit());
|
||||
}, overrides: <Type, Generator>{
|
||||
@ -97,10 +100,11 @@ void main() {
|
||||
fakeCommandRunner.willHaveUncomittedChanges = true;
|
||||
|
||||
final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
|
||||
true,
|
||||
false,
|
||||
gitTagVersion,
|
||||
flutterVersion,
|
||||
force: true,
|
||||
continueFlow: false,
|
||||
testFlow: false,
|
||||
gitTagVersion: gitTagVersion,
|
||||
flutterVersion: flutterVersion,
|
||||
);
|
||||
expect(await result, FlutterCommandResult.success());
|
||||
}, overrides: <Type, Generator>{
|
||||
@ -110,10 +114,11 @@ void main() {
|
||||
|
||||
testUsingContext("Doesn't throw on known tag, dev branch, no force", () async {
|
||||
final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
|
||||
false,
|
||||
false,
|
||||
gitTagVersion,
|
||||
flutterVersion,
|
||||
force: false,
|
||||
continueFlow: false,
|
||||
testFlow: false,
|
||||
gitTagVersion: gitTagVersion,
|
||||
flutterVersion: flutterVersion,
|
||||
);
|
||||
expect(await result, FlutterCommandResult.success());
|
||||
}, overrides: <Type, Generator>{
|
||||
@ -124,10 +129,11 @@ void main() {
|
||||
testUsingContext("Doesn't continue on known tag, dev branch, no force, already up-to-date", () async {
|
||||
fakeCommandRunner.alreadyUpToDate = true;
|
||||
final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
|
||||
false,
|
||||
false,
|
||||
gitTagVersion,
|
||||
flutterVersion,
|
||||
force: false,
|
||||
continueFlow: false,
|
||||
testFlow: false,
|
||||
gitTagVersion: gitTagVersion,
|
||||
flutterVersion: flutterVersion,
|
||||
);
|
||||
expect(await result, FlutterCommandResult.success());
|
||||
verifyNever(globals.processManager.start(
|
||||
|
@ -6,6 +6,7 @@ import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/persistent_tool_state.dart';
|
||||
import 'package:flutter_tools/src/version.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
@ -14,8 +15,8 @@ class MockLogger extends Mock implements Logger {}
|
||||
|
||||
void main() {
|
||||
testWithoutContext('state can be set and persists', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final Directory directory = fs.directory('state_dir');
|
||||
final MemoryFileSystem fileSystem = MemoryFileSystem();
|
||||
final Directory directory = fileSystem.directory('state_dir');
|
||||
directory.createSync();
|
||||
final File stateFile = directory.childFile('.flutter_tool_state');
|
||||
final PersistentToolState state1 = PersistentToolState.test(
|
||||
@ -35,4 +36,28 @@ void main() {
|
||||
);
|
||||
expect(state2.redisplayWelcomeMessage, false);
|
||||
});
|
||||
|
||||
testWithoutContext('channel versions can be cached and stored', () {
|
||||
final MemoryFileSystem fileSystem = MemoryFileSystem();
|
||||
final Directory directory = fileSystem.directory('state_dir')..createSync();
|
||||
final PersistentToolState state1 = PersistentToolState.test(
|
||||
directory: directory,
|
||||
logger: MockLogger(),
|
||||
);
|
||||
|
||||
state1.updateLastActiveVersion('abc', Channel.master);
|
||||
state1.updateLastActiveVersion('def', Channel.dev);
|
||||
state1.updateLastActiveVersion('ghi', Channel.beta);
|
||||
state1.updateLastActiveVersion('jkl', Channel.stable);
|
||||
|
||||
final PersistentToolState state2 = PersistentToolState.test(
|
||||
directory: directory,
|
||||
logger: MockLogger(),
|
||||
);
|
||||
|
||||
expect(state2.lastActiveVersion(Channel.master), 'abc');
|
||||
expect(state2.lastActiveVersion(Channel.dev), 'def');
|
||||
expect(state2.lastActiveVersion(Channel.beta), 'ghi');
|
||||
expect(state2.lastActiveVersion(Channel.stable), 'jkl');
|
||||
});
|
||||
}
|
||||
|
@ -0,0 +1,122 @@
|
||||
// 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 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/io.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/base/process.dart';
|
||||
import 'package:flutter_tools/src/base/terminal.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
|
||||
const String _kInitialVersion = 'v1.9.1+hotfix.6';
|
||||
const String _kBranch = 'stable';
|
||||
const FileSystem fileSystem = LocalFileSystem();
|
||||
const ProcessManager processManager = LocalProcessManager();
|
||||
final ProcessUtils processUtils = ProcessUtils(processManager: processManager, logger: StdoutLogger(
|
||||
terminal: AnsiTerminal(
|
||||
platform: const LocalPlatform(),
|
||||
stdio: const Stdio(),
|
||||
),
|
||||
stdio: const Stdio(),
|
||||
outputPreferences: OutputPreferences.test(wrapText: true),
|
||||
timeoutConfiguration: const TimeoutConfiguration(),
|
||||
));
|
||||
final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
|
||||
|
||||
/// A test for flutter upgrade & downgrade that checks out a parallel flutter repo.
|
||||
void main() {
|
||||
Directory parentDirectory;
|
||||
|
||||
setUp(() {
|
||||
parentDirectory = fileSystem.systemTempDirectory
|
||||
.createTempSync('flutter_tools.');
|
||||
parentDirectory.createSync(recursive: true);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
try {
|
||||
parentDirectory.deleteSync(recursive: true);
|
||||
} on FileSystemException {
|
||||
print('Failed to delete test directory');
|
||||
}
|
||||
});
|
||||
|
||||
test('Can upgrade and downgrade a Flutter checkout', () async {
|
||||
final Directory testDirectory = parentDirectory.childDirectory('flutter');
|
||||
testDirectory.createSync(recursive: true);
|
||||
|
||||
// Enable longpaths for windows integration test.
|
||||
await processManager.run(<String>[
|
||||
'git', 'config', '--system', 'core.longpaths', 'true',
|
||||
]);
|
||||
|
||||
// Step 1. Clone the dev branch of flutter into the test directory.
|
||||
await processUtils.stream(<String>[
|
||||
'git',
|
||||
'clone',
|
||||
'https://github.com/flutter/flutter.git',
|
||||
], workingDirectory: parentDirectory.path, trace: true);
|
||||
|
||||
// Step 2. Switch to the dev branch.
|
||||
await processUtils.stream(<String>[
|
||||
'git',
|
||||
'checkout',
|
||||
'--track',
|
||||
'-b',
|
||||
_kBranch,
|
||||
'origin/$_kBranch',
|
||||
], workingDirectory: testDirectory.path, trace: true);
|
||||
|
||||
// Step 3. Revert to a prior version.
|
||||
await processUtils.stream(<String>[
|
||||
'git',
|
||||
'reset',
|
||||
'--hard',
|
||||
_kInitialVersion,
|
||||
], workingDirectory: testDirectory.path, trace: true);
|
||||
|
||||
// Step 4. Upgrade to the newest dev. This should update the persistent
|
||||
// tool state with the sha for v1.14.3
|
||||
await processUtils.stream(<String>[
|
||||
flutterBin,
|
||||
'upgrade',
|
||||
'--working-directory=${testDirectory.path}'
|
||||
], workingDirectory: testDirectory.path, trace: true);
|
||||
|
||||
// Step 5. Verify that the version is different.
|
||||
final RunResult versionResult = await processUtils.run(<String>[
|
||||
'git',
|
||||
'describe',
|
||||
'--match',
|
||||
'v*.*.*',
|
||||
'--first-parent',
|
||||
'--long',
|
||||
'--tags',
|
||||
], workingDirectory: testDirectory.path);
|
||||
expect(versionResult.stdout, isNot(contains(_kInitialVersion)));
|
||||
|
||||
// Step 6. Downgrade back to initial version.
|
||||
await processUtils.stream(<String>[
|
||||
flutterBin,
|
||||
'downgrade',
|
||||
'--no-prompt',
|
||||
'--working-directory=${testDirectory.path}'
|
||||
], workingDirectory: testDirectory.path, trace: true);
|
||||
|
||||
// Step 7. Verify downgraded version matches original version.
|
||||
final RunResult oldVersionResult = await processUtils.run(<String>[
|
||||
'git',
|
||||
'describe',
|
||||
'--match',
|
||||
'v*.*.*',
|
||||
'--first-parent',
|
||||
'--long',
|
||||
'--tags',
|
||||
], workingDirectory: testDirectory.path);
|
||||
expect(oldVersionResult.stdout, contains(_kInitialVersion));
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user