Skip to content

Commit a7c9320

Browse files
committed
Migration: rework how MigrationCli.run() reports an exit code.
Previously, MigrationCli.run() would always return, but it would set MigrationCli.exitCode to indicate whether an error occurred. This made it necessary to thread a lot of tricky logic around to ensure that we would exit quickly if an error occurred. This CL switches to using an exception, so we get the same effect automatically. Change-Id: I2b885092dec551700900b118ec3d9acd2f3649d7 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/150760 Reviewed-by: Mike Fairhurst <[email protected]>
1 parent a88a328 commit a7c9320

File tree

3 files changed

+67
-62
lines changed

3 files changed

+67
-62
lines changed

pkg/nnbd_migration/bin/migrate.dart

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@ main(List<String> args) async {
1111
var cli = MigrationCli(binaryName: 'nnbd_migration');
1212
ArgResults argResults;
1313
try {
14-
argResults = MigrationCli.createParser().parse(args);
15-
} on FormatException catch (e) {
16-
cli.handleArgParsingException(e);
14+
try {
15+
argResults = MigrationCli.createParser().parse(args);
16+
} on FormatException catch (e) {
17+
cli.handleArgParsingException(e);
18+
}
19+
await cli.run(argResults);
20+
} on MigrationExit catch (migrationExit) {
21+
exitCode = migrationExit.exitCode;
1722
}
18-
if (cli.exitCode == null) await cli.run(argResults);
19-
exitCode = cli.exitCode;
2023
}

pkg/nnbd_migration/lib/migration_cli.dart

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,12 @@ class MigrateCommand extends Command<dynamic> {
195195
@override
196196
FutureOr<int> run() async {
197197
var cli = MigrationCli(binaryName: 'dart $name');
198-
await cli.run(argResults, isVerbose: verbose);
199-
return cli.exitCode;
198+
try {
199+
await cli.run(argResults, isVerbose: verbose);
200+
} on MigrationExit catch (migrationExit) {
201+
return migrationExit.exitCode;
202+
}
203+
return 0;
200204
}
201205
}
202206

@@ -230,10 +234,6 @@ class MigrationCli {
230234
@visibleForTesting
231235
/*late*/ CommandLineOptions options;
232236

233-
/// The exit code that should be used when the process terminates, or `null`
234-
/// if there is still more work to do.
235-
int exitCode;
236-
237237
final Map<String, List<AnalysisError>> fileErrors = {};
238238

239239
final Map<String, LineInfo> lineInfo = {};
@@ -327,7 +327,6 @@ class MigrationCli {
327327
isVerbose ??= argResults[CommandLineOptions.verboseFlag] as bool;
328328
if (argResults[CommandLineOptions.helpFlag] as bool) {
329329
_showUsage(isVerbose);
330-
exitCode = 0;
331330
return;
332331
}
333332
var rest = argResults.rest;
@@ -398,18 +397,16 @@ class MigrationCli {
398397
}
399398
logger.stderr(message);
400399
_showUsage(false);
401-
exitCode = 1;
402-
return;
400+
throw MigrationExit(1);
403401
}
404402

405403
/// Runs the full migration process.
406-
void run(ArgResults argResults, {bool isVerbose}) async {
404+
Future<void> run(ArgResults argResults, {bool isVerbose}) async {
407405
decodeCommandLineArgs(argResults, isVerbose: isVerbose);
408-
if (exitCode != null) return;
406+
if (options == null) return;
409407
if (!options.skipPubOutdated) {
410408
_checkDependencies();
411409
}
412-
if (exitCode != null) return;
413410

414411
logger.stdout('Migrating ${options.directory}');
415412
logger.stdout('');
@@ -447,16 +444,13 @@ class MigrationCli {
447444
if (sdkPathVar != null) {
448445
logger.stdout('$sdkPathEnvironmentVariableSet: $sdkPathVar');
449446
}
450-
exitCode = 1;
447+
throw MigrationExit(1);
451448
}
452-
if (exitCode != null) return;
453449

454450
logger.stdout('');
455451
logger.stdout(ansi.emphasized('Generating migration suggestions...'));
456452
previewUrls = await _fixCodeProcessor.runLaterPhases(resetProgress: true);
457453

458-
if (exitCode != null) return;
459-
460454
if (options.applyChanges) {
461455
logger.stdout(ansi.emphasized('Applying changes:'));
462456

@@ -470,7 +464,6 @@ class MigrationCli {
470464
// Note: do not open the web preview if apply-changes is specified, as we
471465
// currently cannot tell the web preview to disable the "apply migration"
472466
// button.
473-
exitCode = 0;
474467
return;
475468
}
476469

@@ -504,7 +497,6 @@ Use this interactive web view to review, improve, or apply the results.
504497
logger.stdout('To apply these changes, re-run the tool with '
505498
'--${CommandLineOptions.applyChangesFlag}.');
506499
}
507-
exitCode = 0;
508500
}
509501

510502
/// Perform the indicated source edits to the given source, returning the
@@ -547,7 +539,7 @@ Use this interactive web view to review, improve, or apply the results.
547539
options.directory, pathContext, logger, processManager)
548540
.check();
549541
if (!successful) {
550-
exitCode = 1;
542+
throw MigrationExit(1);
551543
}
552544
}
553545

@@ -586,8 +578,7 @@ Use this interactive web view to review, improve, or apply the results.
586578
'Please fix the analysis issues (or, force generation of migration '
587579
'suggestions by re-running with '
588580
'--${CommandLineOptions.ignoreErrorsFlag}).');
589-
exitCode = 1;
590-
return;
581+
throw MigrationExit(1);
591582
}
592583
}
593584
}
@@ -655,7 +646,6 @@ migration anyway due to the use of --${CommandLineOptions.ignoreExceptionsFlag}.
655646
To see exception details, re-run without --${CommandLineOptions.ignoreExceptionsFlag}.
656647
''');
657648
} else {
658-
exitCode = 1;
659649
if (_hasAnalysisErrors) {
660650
logger.stderr('''
661651
Aborting migration due to an exception. This may be due to a bug in
@@ -678,6 +668,7 @@ To attempt to perform migration anyway, you may re-run with
678668
Exception details:
679669
''');
680670
logger.stderr(detail);
671+
throw MigrationExit(1);
681672
}
682673
}
683674

@@ -794,6 +785,14 @@ Exception details:
794785
}
795786
}
796787

788+
/// Exception thrown by [MigrationCli] if the client should exit.
789+
class MigrationExit {
790+
/// The exit code that the client should set.
791+
final int exitCode;
792+
793+
MigrationExit(this.exitCode);
794+
}
795+
797796
/// An abstraction over the static methods on [Process].
798797
///
799798
/// Used in tests to run mock processes.
@@ -871,7 +870,6 @@ class _FixCodeProcessor extends Object {
871870
if (pathsToProcess.contains(unit.path) &&
872871
!pathsProcessed.contains(unit.path)) {
873872
await process(unit);
874-
if (_migrationCli.exitCode != null) return;
875873
pathsProcessed.add(unit.path);
876874
}
877875
}
@@ -888,7 +886,6 @@ class _FixCodeProcessor extends Object {
888886
continue;
889887
}
890888
await process(result);
891-
if (_migrationCli.exitCode != null) return;
892889
}
893890
}
894891

@@ -934,7 +931,7 @@ class _FixCodeProcessor extends Object {
934931
});
935932
}
936933
var state = await _task.finish();
937-
if (_migrationCli.exitCode == null && _migrationCli.options.webPreview) {
934+
if (_migrationCli.options.webPreview) {
938935
await _task.startPreviewServer(state);
939936
}
940937
_progressBar.complete();

pkg/nnbd_migration/test/migration_cli_test.dart

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -152,16 +152,25 @@ mixin _MigrationCliTestMethods on _MigrationCliTestBase {
152152

153153
Future<String> assertDecodeArgsFailure(List<String> args) async {
154154
var cli = _createCli();
155-
await cli.run(MigrationCli.createParser().parse(args));
156-
var stderrText = assertErrorExit(cli);
155+
var stderrText = await assertErrorExit(
156+
cli, () => cli.run(MigrationCli.createParser().parse(args)),
157+
withUsage: true);
157158
expect(stderrText, isNot(contains('Exception')));
158159
return stderrText;
159160
}
160161

161-
String assertErrorExit(MigrationCli cli, {bool withUsage = true}) {
162+
Future<String> assertErrorExit(
163+
MigrationCli cli, Future<void> Function() callback,
164+
{@required bool withUsage, dynamic expectedExitCode = anything}) async {
165+
try {
166+
await callback();
167+
fail('Migration succeeded; expected it to abort with an error');
168+
} on MigrationExit catch (migrationExit) {
169+
expect(migrationExit.exitCode, isNotNull);
170+
expect(migrationExit.exitCode, isNot(0));
171+
expect(migrationExit.exitCode, expectedExitCode);
172+
}
162173
expect(cli.isPreviewServerRunning, isFalse);
163-
expect(cli.exitCode, isNotNull);
164-
expect(cli.exitCode, isNot(0));
165174
var stderrText = logger.stderrBuffer.toString();
166175
expect(stderrText, withUsage ? hasUsageText : isNot(hasUsageText));
167176
expect(stderrText,
@@ -186,7 +195,6 @@ mixin _MigrationCliTestMethods on _MigrationCliTestBase {
186195

187196
void assertNormalExit(MigrationCli cli) {
188197
expect(cli.isPreviewServerRunning, isFalse);
189-
expect(cli.exitCode, 0);
190198
}
191199

192200
Future<String> assertParseArgsFailure(List<String> args) async {
@@ -202,8 +210,9 @@ mixin _MigrationCliTestMethods on _MigrationCliTestBase {
202210
CommandLineOptions assertParseArgsSuccess(List<String> args) {
203211
var cli = _createCli();
204212
cli.decodeCommandLineArgs(MigrationCli.createParser().parse(args));
205-
expect(cli.exitCode, isNull);
213+
assertNormalExit(cli);
206214
var options = cli.options;
215+
expect(options, isNotNull);
207216
return options;
208217
}
209218

@@ -253,6 +262,16 @@ mixin _MigrationCliTestMethods on _MigrationCliTestBase {
253262
expect(success, isTrue);
254263
}
255264

265+
Future<String> assertRunFailure(List<String> args,
266+
{MigrationCli cli,
267+
bool withUsage = false,
268+
dynamic expectedExitCode = anything}) async {
269+
cli ??= _createCli();
270+
return await assertErrorExit(
271+
cli, () => cli.run(MigrationCli.createParser().parse(args)),
272+
withUsage: withUsage, expectedExitCode: expectedExitCode);
273+
}
274+
256275
String createProjectDir(Map<String, String> contents,
257276
{String posixPath = '/test_project'}) {
258277
for (var entry in contents.entries) {
@@ -346,15 +365,14 @@ int${migrated ? '?' : ''} f() => null;
346365
expect(newCoreLibText, isNot(oldCoreLibText));
347366
coreLib.writeAsStringSync(newCoreLibText);
348367
var projectDir = await createProjectDir(simpleProject());
349-
await cli.run(MigrationCli.createParser().parse([projectDir]));
350-
assertErrorExit(cli, withUsage: false);
368+
await assertRunFailure([projectDir], cli: cli);
351369
var output = logger.stdoutBuffer.toString();
352370
expect(output, contains(messages.sdkNnbdOff));
353371
}
354372

355373
test_detect_old_sdk_environment_variable() async {
356374
environmentVariables['SDK_PATH'] = '/fake-old-sdk-path';
357-
var cli = _createCli();
375+
var cli = _createCli(); // Creates the mock SDK as a side effect
358376
// Alter the mock SDK, changing the signature of Object.operator== to match
359377
// the signature that was present prior to NNBD. (This is what the
360378
// migration tool uses to detect an old SDK).
@@ -367,8 +385,7 @@ int${migrated ? '?' : ''} f() => null;
367385
expect(newCoreLibText, isNot(oldCoreLibText));
368386
coreLib.writeAsStringSync(newCoreLibText);
369387
var projectDir = await createProjectDir(simpleProject());
370-
await cli.run(MigrationCli.createParser().parse([projectDir]));
371-
assertErrorExit(cli, withUsage: false);
388+
await assertRunFailure([projectDir], cli: cli);
372389
var output = logger.stdoutBuffer.toString();
373390
expect(output, contains(messages.sdkNnbdOff));
374391
expect(output, contains(messages.sdkPathEnvironmentVariableSet));
@@ -522,8 +539,7 @@ linter:
522539
var projectContents = simpleProject(sourceText: 'main() { print(0); }');
523540
var projectDir = await createProjectDir(projectContents);
524541
var cli = _createCli(injectArtificialException: true);
525-
await cli.run(_parseArgs([projectDir]));
526-
assertErrorExit(cli, withUsage: false);
542+
await assertRunFailure([projectDir], cli: cli);
527543
var errorOutput = logger.stderrBuffer.toString();
528544
expect(errorOutput, contains('Artificial exception triggered'));
529545
expect(
@@ -556,8 +572,7 @@ linter:
556572
simpleProject(sourceText: 'main() { print(0); print(1); }');
557573
var projectDir = await createProjectDir(projectContents);
558574
var cli = _createCli(injectArtificialException: true);
559-
await cli.run(_parseArgs([projectDir]));
560-
assertErrorExit(cli, withUsage: false);
575+
await assertRunFailure([projectDir], cli: cli);
561576
var errorOutput = logger.stderrBuffer.toString();
562577
expect(
563578
'Artificial exception triggered'.allMatches(errorOutput), hasLength(1));
@@ -571,8 +586,7 @@ linter:
571586
simpleProject(sourceText: 'main() { print(0); unresolved; }');
572587
var projectDir = await createProjectDir(projectContents);
573588
var cli = _createCli(injectArtificialException: true);
574-
await cli.run(_parseArgs(['--ignore-errors', projectDir]));
575-
assertErrorExit(cli, withUsage: false);
589+
await assertRunFailure(['--ignore-errors', projectDir], cli: cli);
576590
var errorOutput = logger.stderrBuffer.toString();
577591
expect(errorOutput, contains('Artificial exception triggered'));
578592
expect(errorOutput, contains('try to fix errors in the source code'));
@@ -584,9 +598,7 @@ linter:
584598
int f() => null
585599
''');
586600
var projectDir = await createProjectDir(projectContents);
587-
var cli = _createCli();
588-
await cli.run(_parseArgs([projectDir]));
589-
assertErrorExit(cli, withUsage: false);
601+
await assertRunFailure([projectDir]);
590602
var output = logger.stdoutBuffer.toString();
591603
expect(output, contains('1 analysis issue found'));
592604
var sep = resourceProvider.pathContext.separator;
@@ -1047,11 +1059,8 @@ int f() => null;
10471059
}
10481060
''' /* stdout */,
10491061
'' /* stderr */);
1050-
var cli = _createCli();
1051-
await cli.run(_parseArgs([projectDir]));
1052-
var output = assertErrorExit(cli, withUsage: false);
1062+
var output = await assertRunFailure([projectDir], expectedExitCode: 1);
10531063
expect(output, contains('Warning: dependencies are outdated.'));
1054-
expect(cli.exitCode, 1);
10551064
}
10561065

10571066
test_lifecycle_skip_pub_outdated_enable() async {
@@ -1146,9 +1155,7 @@ import 'package:does_not/exist.dart';
11461155
int f() => null;
11471156
''');
11481157
var projectDir = await createProjectDir(projectContents);
1149-
var cli = _createCli();
1150-
await cli.run(_parseArgs([projectDir]));
1151-
assertErrorExit(cli, withUsage: false);
1158+
await assertRunFailure([projectDir]);
11521159
var output = logger.stdoutBuffer.toString();
11531160
expect(output, contains('1 analysis issue found'));
11541161
expect(output, contains('uri_does_not_exist'));
@@ -1199,9 +1206,7 @@ int f() => null;
11991206
}
12001207

12011208
test_migrate_path_two() async {
1202-
var cli = _createCli();
1203-
await cli.run(_parseArgs(['foo', 'bar']));
1204-
var stderrText = assertErrorExit(cli);
1209+
var stderrText = await assertRunFailure(['foo', 'bar'], withUsage: true);
12051210
expect(stderrText, contains('No more than one path may be specified'));
12061211
}
12071212

@@ -1581,7 +1586,7 @@ name: test
15811586
var cli = _createCli();
15821587
await cli.run(_parseArgs(
15831588
['--${CommandLineOptions.helpFlag}', if (verbose) '--verbose']));
1584-
expect(cli.exitCode, 0);
1589+
assertNormalExit(cli);
15851590
var helpText = logger.stderrBuffer.toString();
15861591
return helpText;
15871592
}

0 commit comments

Comments
 (0)