Skip to content

Commit

Permalink
[flutter_conductor] allow --force to override validations on start su…
Browse files Browse the repository at this point in the history
…b-command (flutter#93514)
  • Loading branch information
christopherfujino authored Nov 13, 2021
1 parent e95d669 commit 764577f
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 2 deletions.
13 changes: 13 additions & 0 deletions dev/conductor/core/lib/src/globals.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'proto/conductor_state.pb.dart' as pb;
const String gsutilBinary = 'gsutil.py';

const String kFrameworkDefaultBranch = 'master';
const String kForceFlag = 'force';

const List<String> kReleaseChannels = <String>[
'stable',
Expand Down Expand Up @@ -81,6 +82,18 @@ String? getValueFromEnvOrArgs(
'to be provided!');
}

bool getBoolFromEnvOrArgs(
String name,
ArgResults argResults,
Map<String, String> env,
) {
final String envName = fromArgToEnvName(name);
if (env[envName] != null) {
return (env[envName]?.toUpperCase()) == 'TRUE';
}
return argResults[name] as bool;
}

/// Return multiple values from the environment or fall back to [argResults].
///
/// Values read from an environment variable are assumed to be comma-delimited.
Expand Down
1 change: 0 additions & 1 deletion dev/conductor/core/lib/src/next.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import './stdio.dart';

const String kStateOption = 'state-file';
const String kYesFlag = 'yes';
const String kForceFlag = 'force';

/// Command to proceed from one [pb.ReleasePhase] to the next.
class NextCommand extends Command<void> {
Expand Down
22 changes: 21 additions & 1 deletion dev/conductor/core/lib/src/start.dart
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ class StartCommand extends Command<void> {
'n': 'Indicates a hotfix to a dev or beta release.',
},
);
argParser.addFlag(
kForceFlag,
abbr: 'f',
help: 'Override all validations of the command line inputs.',
);
}

final Checkouts checkouts;
Expand Down Expand Up @@ -178,6 +183,11 @@ class StartCommand extends Command<void> {
argumentResults,
platform.environment,
)!;
final bool force = getBoolFromEnvOrArgs(
kForceFlag,
argumentResults,
platform.environment,
);
final File stateFile = checkouts.fileSystem.file(
getValueFromEnvOrArgs(kStateOption, argumentResults, platform.environment),
);
Expand All @@ -198,6 +208,7 @@ class StartCommand extends Command<void> {
releaseChannel: releaseChannel,
stateFile: stateFile,
stdio: stdio,
force: force,
);
return context.run();
}
Expand All @@ -223,6 +234,7 @@ class StartContext {
required this.releaseChannel,
required this.stateFile,
required this.stdio,
this.force = false,
}) : git = Git(processManager);

final String candidateBranch;
Expand All @@ -242,6 +254,9 @@ class StartContext {
final File stateFile;
final Stdio stdio;

/// If validations should be overridden.
final bool force;

Future<void> run() async {
if (stateFile.existsSync()) {
throw ConductorException(
Expand Down Expand Up @@ -361,7 +376,12 @@ class StartContext {
final Version lastVersion = Version.fromString(await framework.getFullTag(
framework.upstreamRemote.name, candidateBranch,
exact: false,
))..ensureValid(candidateBranch, incrementLetter);
));
// [force] means we know this would fail but need to publish anyway
if (!force) {
lastVersion.ensureValid(candidateBranch, incrementLetter);
}

Version nextVersion = calculateNextVersion(lastVersion);
nextVersion = await ensureBranchPointTagged(nextVersion, framework);

Expand Down
6 changes: 6 additions & 0 deletions dev/conductor/core/lib/src/version.dart
Original file line number Diff line number Diff line change
Expand Up @@ -238,12 +238,18 @@ class Version {
final int y;

/// Number of hotfix releases after a stable release.
///
/// For non-stable releases, this will be 0.
final int z;

/// Zero-indexed count of dev releases after a beta release.
///
/// For stable releases, this will be null.
final int? m;

/// Number of hotfixes required to make a dev release.
///
/// For stable releases, this will be null.
final int? n;

/// Number of commits past last tagged dev release.
Expand Down
66 changes: 66 additions & 0 deletions dev/conductor/core/test/globals_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:args/args.dart';
import 'package:conductor_core/src/globals.dart';
import 'package:conductor_core/src/proto/conductor_state.pb.dart' as pb;

Expand Down Expand Up @@ -129,4 +130,69 @@ void main() {
);
});
});

group('getBoolFromEnvOrArgs', () {
const String flagName = 'a-cli-flag';

test('prefers env over argResults', () {
final ArgResults argResults = FakeArgs(results: <String, Object>{
flagName: false,
});
final Map<String, String> env = <String, String>{'A_CLI_FLAG': 'TRUE'};
final bool result = getBoolFromEnvOrArgs(
flagName,
argResults,
env,
);
expect(result, true);
});

test('falls back to argResults if env is empty', () {
final ArgResults argResults = FakeArgs(results: <String, Object>{
flagName: false,
});
final Map<String, String> env = <String, String>{};
final bool result = getBoolFromEnvOrArgs(
flagName,
argResults,
env,
);
expect(result, false);
});
});
}

class FakeArgs implements ArgResults {
FakeArgs({
this.arguments = const <String>[],
this.name = 'fake-command',
this.results = const <String, Object>{},
});

final Map<String, Object> results;

@override
final List<String> arguments;

@override
final String name;

@override
ArgResults? get command => throw Exception('Unimplemented');

@override
List<String> get rest => throw Exception('Unimplemented');

@override
Iterable<String> get options => throw Exception('Unimplemented');

@override
bool wasParsed(String name) {
return results[name] != null;
}

@override
Object? operator[](String name) {
return results[name];
}
}
183 changes: 183 additions & 0 deletions dev/conductor/core/test/start_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import 'dart:convert' show jsonDecode;

import 'package:args/command_runner.dart';
import 'package:conductor_core/src/globals.dart';
import 'package:conductor_core/src/proto/conductor_state.pb.dart' as pb;
import 'package:conductor_core/src/proto/conductor_state.pbenum.dart' show ReleasePhase;
import 'package:conductor_core/src/repository.dart';
Expand Down Expand Up @@ -122,6 +123,188 @@ void main() {
);
});

test('does not fail if version wrong but --force provided', () async {
const String revision2 = 'def789';
const String revision3 = '123abc';
const String previousDartRevision = '171876a4e6cf56ee6da1f97d203926bd7afda7ef';
const String nextDartRevision = 'f6c91128be6b77aef8351e1e3a9d07c85bc2e46e';
const String previousVersion = '1.2.0-1.0.pre';
// This is what this release will be
const String nextVersion = '1.2.0-1.1.pre';
const String incrementLevel = 'n';

final Directory engine = fileSystem.directory(checkoutsParentDirectory)
.childDirectory('flutter_conductor_checkouts')
.childDirectory('engine');

final File depsFile = engine.childFile('DEPS');

final List<FakeCommand> engineCommands = <FakeCommand>[
FakeCommand(
command: <String>[
'git',
'clone',
'--origin',
'upstream',
'--',
EngineRepository.defaultUpstream,
engine.path,
],
onRun: () {
// Create the DEPS file which the tool will update
engine.createSync(recursive: true);
depsFile.writeAsStringSync(generateMockDeps(previousDartRevision));
}
),
const FakeCommand(
command: <String>['git', 'remote', 'add', 'mirror', engineMirror],
),
const FakeCommand(
command: <String>['git', 'fetch', 'mirror'],
),
const FakeCommand(
command: <String>['git', 'checkout', 'upstream/$candidateBranch'],
),
const FakeCommand(
command: <String>['git', 'rev-parse', 'HEAD'],
stdout: revision2,
),
const FakeCommand(
command: <String>[
'git',
'checkout',
'-b',
'cherrypicks-$candidateBranch',
],
),
const FakeCommand(
command: <String>['git', 'status', '--porcelain'],
stdout: 'MM path/to/DEPS',
),
const FakeCommand(
command: <String>['git', 'add', '--all'],
),
const FakeCommand(
command: <String>['git', 'commit', "--message='Update Dart SDK to $nextDartRevision'"],
),
const FakeCommand(
command: <String>['git', 'rev-parse', 'HEAD'],
stdout: revision2,
),
const FakeCommand(
command: <String>['git', 'rev-parse', 'HEAD'],
stdout: revision2,
),
];

final List<FakeCommand> frameworkCommands = <FakeCommand>[
FakeCommand(
command: <String>[
'git',
'clone',
'--origin',
'upstream',
'--',
FrameworkRepository.defaultUpstream,
fileSystem.path.join(
checkoutsParentDirectory,
'flutter_conductor_checkouts',
'framework',
),
],
),
const FakeCommand(
command: <String>['git', 'remote', 'add', 'mirror', frameworkMirror],
),
const FakeCommand(
command: <String>['git', 'fetch', 'mirror'],
),
const FakeCommand(
command: <String>['git', 'checkout', 'upstream/$candidateBranch'],
),
const FakeCommand(
command: <String>['git', 'rev-parse', 'HEAD'],
stdout: revision3,
),
const FakeCommand(
command: <String>[
'git',
'checkout',
'-b',
'cherrypicks-$candidateBranch',
],
),
const FakeCommand(
command: <String>[
'git',
'describe',
'--match',
'*.*.*',
'--tags',
'refs/remotes/upstream/$candidateBranch',
],
stdout: '$previousVersion-42-gabc123',
),
const FakeCommand(
command: <String>['git', 'rev-parse', 'HEAD'],
stdout: revision3,
),
];

final CommandRunner<void> runner = createRunner(
commands: <FakeCommand>[
...engineCommands,
...frameworkCommands,
],
);

final String stateFilePath = fileSystem.path.join(
platform.environment['HOME']!,
kStateFileName,
);

await runner.run(<String>[
'start',
'--$kFrameworkMirrorOption',
frameworkMirror,
'--$kEngineMirrorOption',
engineMirror,
'--$kCandidateOption',
candidateBranch,
'--$kReleaseOption',
releaseChannel,
'--$kStateOption',
stateFilePath,
'--$kDartRevisionOption',
nextDartRevision,
'--$kIncrementOption',
incrementLevel,
'--$kForceFlag',
]);

final File stateFile = fileSystem.file(stateFilePath);

final pb.ConductorState state = pb.ConductorState();
state.mergeFromProto3Json(
jsonDecode(stateFile.readAsStringSync()),
);

expect(processManager.hasRemainingExpectations, false);
expect(state.isInitialized(), true);
expect(state.releaseChannel, releaseChannel);
expect(state.releaseVersion, nextVersion);
expect(state.engine.candidateBranch, candidateBranch);
expect(state.engine.startingGitHead, revision2);
expect(state.engine.dartRevision, nextDartRevision);
expect(state.engine.upstream.url, '[email protected]:flutter/engine.git');
expect(state.framework.candidateBranch, candidateBranch);
expect(state.framework.startingGitHead, revision3);
expect(state.framework.upstream.url, '[email protected]:flutter/flutter.git');
expect(state.currentPhase, ReleasePhase.APPLY_ENGINE_CHERRYPICKS);
expect(state.conductorVersion, conductorVersion);
expect(state.incrementLevel, incrementLevel);
});

test('creates state file if provided correct inputs', () async {
const String revision2 = 'def789';
const String revision3 = '123abc';
Expand Down

0 comments on commit 764577f

Please sign in to comment.