// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file // for details. 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:async'; import 'dart:convert'; import 'dart:io'; import 'package:analyzer/dart/analysis/features.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/file_system/file_system.dart' show ResourceProvider; import 'package:analyzer/file_system/memory_file_system.dart'; import 'package:analyzer/file_system/physical_file_system.dart'; import 'package:analyzer/source/line_info.dart'; import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart'; import 'package:analyzer/src/test_utilities/mock_sdk.dart' as mock_sdk; import 'package:args/args.dart'; import 'package:cli_util/cli_logging.dart'; import 'package:http/http.dart' as http; import 'package:meta/meta.dart'; import 'package:nnbd_migration/instrumentation.dart'; import 'package:nnbd_migration/migration_cli.dart'; import 'package:nnbd_migration/src/front_end/dartfix_listener.dart'; import 'package:nnbd_migration/src/front_end/instrumentation_listener.dart'; import 'package:nnbd_migration/src/front_end/migration_summary.dart'; import 'package:nnbd_migration/src/front_end/non_nullable_fix.dart'; import 'package:nnbd_migration/src/front_end/web/edit_details.dart'; import 'package:nnbd_migration/src/front_end/web/file_details.dart'; import 'package:nnbd_migration/src/front_end/web/navigation_tree.dart'; import 'package:nnbd_migration/src/messages.dart' as messages; import 'package:nnbd_migration/src/preview/preview_site.dart'; import 'package:path/path.dart' as path; import 'package:test/test.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; import 'utilities/test_logger.dart'; main() { defineReflectiveSuite(() { defineReflectiveTests(_MigrationCliTestPosix); defineReflectiveTests(_MigrationCliTestWindows); }); } /// Specialization of [InstrumentationListener] that generates artificial /// exceptions, so that we can test they are properly propagated to top level. class _ExceptionGeneratingInstrumentationListener extends InstrumentationListener { _ExceptionGeneratingInstrumentationListener( {MigrationSummary migrationSummary}) : super(migrationSummary: migrationSummary); @override void externalDecoratedType(Element element, DecoratedTypeInfo decoratedType) { if (element.name == 'print') { throw StateError('Artificial exception triggered'); } super.externalDecoratedType(element, decoratedType); } } /// Specialization of [NonNullableFix] that generates artificial exceptions, so /// that we can test they are properly propagated to top level. class _ExceptionGeneratingNonNullableFix extends NonNullableFix { _ExceptionGeneratingNonNullableFix( DartFixListener listener, ResourceProvider resourceProvider, LineInfo Function(String) getLineInfo, Object bindAddress, Logger logger, {List included = const [], int preferredPort, String summaryPath, @required String sdkPath}) : super(listener, resourceProvider, getLineInfo, bindAddress, logger, (String path) => true, included: included, preferredPort: preferredPort, summaryPath: summaryPath, sdkPath: sdkPath); @override InstrumentationListener createInstrumentationListener( {MigrationSummary migrationSummary}) => _ExceptionGeneratingInstrumentationListener( migrationSummary: migrationSummary); } class _MigrationCli extends MigrationCli { final _MigrationCliTestBase _test; /// If non-null, callback function that will be invoked by the `applyHook` /// override. void Function() _onApplyHook; _MigrationCli(this._test) : super( binaryName: 'nnbd_migration', loggerFactory: (isVerbose) => _test.logger = TestLogger(isVerbose), defaultSdkPathOverride: _test.resourceProvider.convertPath(mock_sdk.sdkRoot), resourceProvider: _test.resourceProvider, environmentVariables: _test.environmentVariables); _MigrationCliRunner decodeCommandLineArgs(ArgResults argResults, {bool isVerbose}) { var runner = super.decodeCommandLineArgs(argResults, isVerbose: isVerbose); if (runner == null) return null; return _MigrationCliRunner(this, runner.options); } } class _MigrationCliRunner extends MigrationCliRunner { Future Function() _runWhilePreviewServerActive; _MigrationCliRunner(_MigrationCli cli, CommandLineOptions options) : super(cli, options); _MigrationCli get cli => super.cli as _MigrationCli; @override void applyHook() { super.applyHook(); cli._onApplyHook?.call(); } @override Object computeBindAddress() { var address = super.computeBindAddress(); if (Platform.environment.containsKey('FORCE_IPV6') && address == InternetAddress.loopbackIPv4) { return InternetAddress.loopbackIPv6; } return address; } @override Set computePathsToProcess(DriverBasedAnalysisContext context) => cli._test.overridePathsToProcess ?? _sortPaths(super.computePathsToProcess(context)); @override NonNullableFix createNonNullableFix( DartFixListener listener, ResourceProvider resourceProvider, LineInfo Function(String path) getLineInfo, Object bindAddress, {List included = const [], int preferredPort, String summaryPath, @required String sdkPath}) { if (cli._test.injectArtificialException) { return _ExceptionGeneratingNonNullableFix( listener, resourceProvider, getLineInfo, bindAddress, logger, included: included, preferredPort: preferredPort, summaryPath: summaryPath, sdkPath: sdkPath); } else { return super.createNonNullableFix( listener, resourceProvider, getLineInfo, bindAddress, included: included, preferredPort: preferredPort, summaryPath: summaryPath, sdkPath: sdkPath); } } @override void listenForSignalInterrupt() { if (_runWhilePreviewServerActive == null) { fail('Preview server not expected to have been started'); } sigIntSignalled = Completer(); _runWhilePreviewServerActive.call().then((_) => sigIntSignalled.complete()); _runWhilePreviewServerActive = null; } Future runWithPreviewServer(Future Function() callback) async { _runWhilePreviewServerActive = callback; await run(); if (_runWhilePreviewServerActive != null) { fail('Preview server never started'); } } @override bool shouldBeMigrated(String path) => cli._test.overrideShouldBeMigrated?.call(path) ?? super.shouldBeMigrated(path); /// Sorts the paths in [paths] for repeatability of migration tests. Set _sortPaths(Set paths) { var pathList = paths.toList(); pathList.sort(); return pathList.toSet(); } } abstract class _MigrationCliTestBase { Map environmentVariables = {}; /// If `true`, then an artificial exception should be generated when migration /// encounters a reference to the `print` function. bool injectArtificialException = false; /// If non-null, this is injected as the return value for /// [_MigrationCliRunner.computePathsToProcess]. Set overridePathsToProcess; bool Function(String) overrideShouldBeMigrated; set logger(TestLogger logger); MemoryResourceProvider get resourceProvider; } mixin _MigrationCliTestMethods on _MigrationCliTestBase { @override /*late*/ TestLogger logger; final hasVerboseHelpMessage = contains('for verbose help output'); final hasUsageText = contains('Usage: nnbd_migration'); final urlStartRegexp = RegExp('https?:'); final dartVersionIsNullSafeByDefault = Feature.non_nullable.releaseVersion != null; String assertDecodeArgsFailure(List args) { var cli = _createCli(); try { cli.decodeCommandLineArgs(MigrationCli.createParser().parse(args)); fail('Migration succeeded; expected it to abort with an error'); } on MigrationExit catch (migrationExit) { expect(migrationExit.exitCode, isNotNull); expect(migrationExit.exitCode, isNot(0)); } var stderrText = logger.stderrBuffer.toString(); expect(stderrText, hasUsageText); expect(stderrText, hasVerboseHelpMessage); return stderrText; } Future assertErrorExit( MigrationCliRunner cliRunner, FutureOr Function() callback, {@required bool withUsage, dynamic expectedExitCode}) async { expectedExitCode ??= isNot(0); try { await callback(); fail('Migration succeeded; expected it to abort with an error'); } on MigrationExit catch (migrationExit) { expect(migrationExit.exitCode, isNotNull); expect(migrationExit.exitCode, expectedExitCode); } expect(cliRunner.isPreviewServerRunning, isFalse); return assertStderr(withUsage: withUsage); } void assertHttpSuccess(http.Response response) { if (response.statusCode == 500) { try { var decodedResponse = jsonDecode(response.body); print('Exception: ${decodedResponse['exception']}'); print('Stack trace:'); print(decodedResponse['stackTrace']); } catch (_) { print(response.body); } fail('HTTP request failed'); } expect(response.statusCode, 200); } void assertNormalExit(MigrationCliRunner cliRunner) { expect(cliRunner.isPreviewServerRunning, isFalse); } Future assertParseArgsFailure(List args) async { try { MigrationCli.createParser().parse(args); } on FormatException catch (e) { // Parsing failed, which was expected. return e.message; } fail('Parsing was expected to fail, but did not'); } CommandLineOptions assertParseArgsSuccess(List args) { var cliRunner = _createCli() .decodeCommandLineArgs(MigrationCli.createParser().parse(args)); assertNormalExit(cliRunner); var options = cliRunner.options; expect(options, isNotNull); return options; } Future assertPreviewServerResponsive(String url) async { var response = await httpGet(Uri.parse(url)); assertHttpSuccess(response); } void assertProjectContents(String projectDir, Map expected) { for (var entry in expected.entries) { var relativePathPosix = entry.key; assert(!path.posix.isAbsolute(relativePathPosix)); var filePath = resourceProvider.pathContext .join(projectDir, resourceProvider.convertPath(relativePathPosix)); expect( resourceProvider.getFile(filePath).readAsStringSync(), entry.value); } } Future assertRunFailure(List args, {MigrationCli cli, bool withUsage = false, dynamic expectedExitCode}) async { expectedExitCode ??= isNot(0); cli ??= _createCli(); MigrationCliRunner cliRunner; try { cliRunner = cli.decodeCommandLineArgs(MigrationCli.createParser().parse(args)); } on MigrationExit catch (e) { expect(e.exitCode, isNotNull); expect(e.exitCode, expectedExitCode); return assertStderr(withUsage: withUsage); } return await assertErrorExit(cliRunner, () => cliRunner.run(), withUsage: withUsage, expectedExitCode: expectedExitCode); } String assertStderr({@required bool withUsage}) { var stderrText = logger.stderrBuffer.toString(); expect(stderrText, withUsage ? hasUsageText : isNot(hasUsageText)); expect(stderrText, withUsage ? hasVerboseHelpMessage : isNot(hasVerboseHelpMessage)); return stderrText; } /// Wraps a future containing an HTTP response so that when that response is /// received, we will verify that it is reasonable. Future checkHttpResponse( Future futureResponse) async { var response = await futureResponse; // Check that all "http:" and "https:" URLs in the given HTTP response are // absolute (guards against https://github.com/dart-lang/sdk/issues/43545). for (var match in urlStartRegexp.allMatches(response.body)) { expect(response.body.substring(match.end), startsWith('//')); } return response; } String createProjectDir(Map contents, {String posixPath = '/test_project'}) { for (var entry in contents.entries) { var relativePathPosix = entry.key; assert(!path.posix.isAbsolute(relativePathPosix)); var filePathPosix = path.posix.join(posixPath, relativePathPosix); resourceProvider.newFile( resourceProvider.convertPath(filePathPosix), entry.value); } return resourceProvider.convertPath(posixPath); } Future getSourceFromServer(Uri uri, String path) async { http.Response response = await tryGetSourceFromServer(uri, path); assertHttpSuccess(response); return jsonDecode(response.body)['sourceCode'] as String; } /// Performs an HTTP get, verifying that the response received (if any) is /// reasonable. Future httpGet(Uri url, {Map headers}) { return checkHttpResponse(http.get(url, headers: headers)); } /// Performs an HTTP post, verifying that the response received (if any) is /// reasonable. Future httpPost(Uri url, {Map headers, dynamic body, Encoding encoding}) { return checkHttpResponse( http.post(url, headers: headers, body: body, encoding: encoding)); } String packagePath(String path) => resourceProvider.convertPath('/.pub-cache/$path'); Future runWithPreviewServer(_MigrationCli cli, List args, Future Function(String) callback) async { String url; var cliRunner = cli.decodeCommandLineArgs(_parseArgs(args)); if (cliRunner != null) { await cliRunner.runWithPreviewServer(() async { // Server should be running now url = RegExp('http://.*', multiLine: true) .stringMatch(logger.stdoutBuffer.toString()); await callback(url); }); // Server should be stopped now expect(httpGet(Uri.parse(url)), throwsA(anything)); assertNormalExit(cliRunner); } } void setUp() { resourceProvider.newFolder(resourceProvider.pathContext.current); environmentVariables.clear(); } Map simpleProject( {bool migrated = false, String sourceText, String pubspecText, String packageConfigText, String analysisOptionsText}) { return { 'pubspec.yaml': pubspecText ?? ''' name: test environment: sdk: '${migrated ? '>=2.12.0 <3.0.0' : '>=2.6.0 <3.0.0'}' ''', '.dart_tool/package_config.json': packageConfigText ?? _getPackageConfigText(migrated: migrated), 'lib/test.dart': sourceText ?? ''' int${migrated ? '?' : ''} f() => null; ''', if (analysisOptionsText != null) 'analysis_options.yaml': analysisOptionsText, }; } void tearDown() { NonNullableFix.shutdownAllServers(); } test_default_logger() { // When running normally, we don't override the logger; make sure it has a // non-null default so that there won't be a crash. expect(MigrationCli(binaryName: 'nnbd_migration').logger, isNotNull); } test_detect_old_sdk() async { var cli = _createCli(); // Alter the mock SDK, changing the signature of Object.operator== to match // the signature that was present prior to NNBD. (This is what the // migration tool uses to detect an old SDK). var coreLib = resourceProvider.getFile( resourceProvider.convertPath('${mock_sdk.sdkRoot}/lib/core/core.dart')); var oldCoreLibText = coreLib.readAsStringSync(); var newCoreLibText = oldCoreLibText.replaceAll( 'external bool operator ==(Object other)', 'external bool operator ==(dynamic other)'); expect(newCoreLibText, isNot(oldCoreLibText)); coreLib.writeAsStringSync(newCoreLibText); var projectDir = createProjectDir(simpleProject()); await assertRunFailure([projectDir], cli: cli); var output = logger.stdoutBuffer.toString(); expect(output, contains(messages.sdkNnbdOff)); } test_detect_old_sdk_environment_variable() async { environmentVariables['SDK_PATH'] = '/fake-old-sdk-path'; var cli = _createCli(); // Creates the mock SDK as a side effect // Alter the mock SDK, changing the signature of Object.operator== to match // the signature that was present prior to NNBD. (This is what the // migration tool uses to detect an old SDK). var coreLib = resourceProvider.getFile( resourceProvider.convertPath('${mock_sdk.sdkRoot}/lib/core/core.dart')); var oldCoreLibText = coreLib.readAsStringSync(); var newCoreLibText = oldCoreLibText.replaceAll( 'external bool operator ==(Object other)', 'external bool operator ==(dynamic other)'); expect(newCoreLibText, isNot(oldCoreLibText)); coreLib.writeAsStringSync(newCoreLibText); var projectDir = createProjectDir(simpleProject()); await assertRunFailure([projectDir], cli: cli); var output = logger.stdoutBuffer.toString(); expect(output, contains(messages.sdkNnbdOff)); expect(output, contains(messages.sdkPathEnvironmentVariableSet)); expect(output, contains(environmentVariables['SDK_PATH'])); } test_flag_apply_changes_default() { expect(assertParseArgsSuccess([]).applyChanges, isFalse); expect(assertParseArgsSuccess([]).webPreview, isTrue); } test_flag_apply_changes_disable() async { // "--no-apply-changes" is not an option. await assertParseArgsFailure(['--no-apply-changes']); } test_flag_apply_changes_enable() { var options = assertParseArgsSuccess(['--apply-changes']); expect(options.applyChanges, isTrue); expect(options.webPreview, isFalse); } test_flag_apply_changes_enable_with_no_web_preview() { expect( assertParseArgsSuccess(['--no-web-preview', '--apply-changes']) .applyChanges, isTrue); } test_flag_apply_changes_incompatible_with_web_preview() { expect(assertDecodeArgsFailure(['--web-preview', '--apply-changes']), contains('--apply-changes requires --no-web-preview')); } test_flag_help() { var helpText = _getHelpText(verbose: false); expect(helpText, hasUsageText); expect(helpText, hasVerboseHelpMessage); } test_flag_help_verbose() { var helpText = _getHelpText(verbose: true); expect(helpText, hasUsageText); expect(helpText, isNot(hasVerboseHelpMessage)); } test_flag_ignore_errors_default() { expect(assertParseArgsSuccess([]).ignoreErrors, isFalse); } test_flag_ignore_errors_disable() async { await assertParseArgsFailure(['--no-ignore-errors']); } test_flag_ignore_errors_enable() { expect(assertParseArgsSuccess(['--ignore-errors']).ignoreErrors, isTrue); } test_flag_ignore_exceptions_default() { expect(assertParseArgsSuccess([]).ignoreExceptions, isFalse); } test_flag_ignore_exceptions_disable() async { await assertParseArgsFailure(['--no-ignore-exceptions']); } test_flag_ignore_exceptions_enable() { expect(assertParseArgsSuccess(['--ignore-exceptions']).ignoreExceptions, isTrue); } test_flag_ignore_exceptions_hidden() { var flagName = '--ignore-exceptions'; expect(_getHelpText(verbose: false), isNot(contains(flagName))); expect(_getHelpText(verbose: true), contains(flagName)); } test_flag_skip_import_check_default() { expect(assertParseArgsSuccess([]).skipImportCheck, isFalse); } test_flag_skip_import_check_disable() async { // "--no-skip-import-check" is not an option. await assertParseArgsFailure(['--no-skip-import_check']); } test_flag_skip_import_check_enable() { expect(assertParseArgsSuccess(['--skip-import-check']).skipImportCheck, isTrue); } test_flag_web_preview_default() { expect(assertParseArgsSuccess([]).webPreview, isTrue); } test_flag_web_preview_disable() { expect(assertParseArgsSuccess(['--no-web-preview']).webPreview, isFalse); } test_flag_web_preview_enable() { expect(assertParseArgsSuccess(['--web-preview']).webPreview, isTrue); } test_lifecycle_already_migrated_file() async { Map createProject({bool migrated = false}) { var projectContents = simpleProject(sourceText: ''' ${migrated ? '' : '// @dart = 2.6'} import 'already_migrated.dart'; int${migrated ? '?' : ''} x = y; ''', migrated: true); projectContents['lib/already_migrated.dart'] = ''' int? y = 0; '''; return projectContents; } var projectContents = createProject(); var projectDir = createProjectDir(projectContents); var cliRunner = _createCli(nullSafePackages: ['test']) .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); assertNormalExit(cliRunner); // Check that a summary was printed expect(logger.stdoutBuffer.toString(), contains('Applying changes')); // And that it refers to test.dart, but not pubspec.yaml or // already_migrated.dart. expect(logger.stdoutBuffer.toString(), contains('test.dart')); expect(logger.stdoutBuffer.toString(), isNot(contains('pubspec.yaml'))); expect(logger.stdoutBuffer.toString(), isNot(contains('already_migrated.dart'))); // And that it does not tell the user they can rerun with `--apply-changes` expect(logger.stdoutBuffer.toString(), isNot(contains('--apply-changes'))); // Check that the non-migrated library was changed but not the migrated one assertProjectContents(projectDir, createProject(migrated: true)); } test_lifecycle_apply_changes() async { var projectContents = simpleProject(); var projectDir = createProjectDir(projectContents); var cli = _createCli(); var cliRunner = cli.decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); bool applyHookCalled = false; cli._onApplyHook = () { expect(applyHookCalled, false); applyHookCalled = true; // Changes should have been made assertProjectContents(projectDir, simpleProject(migrated: true)); }; await cliRunner.run(); assertNormalExit(cliRunner); expect(applyHookCalled, true); // Check that a summary was printed expect(logger.stdoutBuffer.toString(), contains('Applying changes')); // And that it refers to test.dart and pubspec.yaml expect(logger.stdoutBuffer.toString(), contains('test.dart')); expect(logger.stdoutBuffer.toString(), contains('pubspec.yaml')); // And that it does not tell the user they can rerun with `--apply-changes` expect(logger.stdoutBuffer.toString(), isNot(contains('--apply-changes'))); } test_lifecycle_contextdiscovery_handles_multiple() async { var projectContents = simpleProject(); var subProject = simpleProject(); for (var filePath in subProject.keys) { projectContents['example/$filePath'] = subProject[filePath]; } projectContents['example/analysis_options.yaml'] = ''' analyzer: strong-mode: implicit-casts: false linter: rules: - empty_constructor_bodies '''; var projectDir = createProjectDir(projectContents); var cliRunner = _createCli() .decodeCommandLineArgs(_parseArgs(['--no-web-preview', projectDir])); await cliRunner.run(); assertNormalExit(cliRunner); expect(cliRunner.hasMultipleAnalysisContext, true); expect(cliRunner.analysisContext, isNotNull); var output = logger.stdoutBuffer.toString(); expect(output, contains('more than one project found')); } test_lifecycle_contextdiscovery_handles_single() async { var projectContents = simpleProject(); var projectDir = createProjectDir(projectContents); var cliRunner = _createCli() .decodeCommandLineArgs(_parseArgs(['--no-web-preview', projectDir])); await cliRunner.run(); assertNormalExit(cliRunner); expect(cliRunner.hasMultipleAnalysisContext, false); expect(cliRunner.analysisContext, isNotNull); } test_lifecycle_exception_handling() async { var projectContents = simpleProject(sourceText: 'main() { print(0); }'); var projectDir = createProjectDir(projectContents); injectArtificialException = true; await assertRunFailure([projectDir]); var errorOutput = logger.stderrBuffer.toString(); expect(errorOutput, contains('Artificial exception triggered')); expect( errorOutput, isNot(contains('try to fix errors in the source code'))); expect(errorOutput, contains('re-run with\n--ignore-exceptions')); expect(errorOutput, contains('consider filing a bug report')); expect( errorOutput, contains( RegExp(r'Please include the SDK version \([0-9]+\.[0-9]+\..*\)'))); } test_lifecycle_exception_handling_ignore() async { var projectContents = simpleProject(sourceText: ''' main() { print(0); int x = null; } '''); var projectDir = createProjectDir(projectContents); injectArtificialException = true; var cli = _createCli(); await runWithPreviewServer(cli, ['--ignore-exceptions', projectDir], (url) async { var output = logger.stdoutBuffer.toString(); expect(output, contains('No analysis issues found')); expect(output, isNot(contains('Artificial exception triggered'))); expect( output, contains('Attempting to perform\nmigration anyway due to the use' ' of --ignore-exceptions.')); expect(output, contains('re-run without --ignore-exceptions')); await assertPreviewServerResponsive(url); await _tellPreviewToApplyChanges(url); assertProjectContents( projectDir, simpleProject(migrated: true, sourceText: ''' main() { print(0); int? x = null; } ''')); }); expect(logger.stderrBuffer.toString(), isEmpty); } test_lifecycle_exception_handling_multiple() async { var projectContents = simpleProject(sourceText: 'main() { print(0); print(1); }'); var projectDir = createProjectDir(projectContents); injectArtificialException = true; await assertRunFailure([projectDir]); var errorOutput = logger.stderrBuffer.toString(); expect( 'Artificial exception triggered'.allMatches(errorOutput), hasLength(1)); expect( errorOutput, isNot(contains('try to fix errors in the source code'))); expect(errorOutput, contains('re-run with\n--ignore-exceptions')); } test_lifecycle_exception_handling_with_error() async { var projectContents = simpleProject(sourceText: 'main() { print(0); unresolved; }'); var projectDir = createProjectDir(projectContents); injectArtificialException = true; await assertRunFailure(['--ignore-errors', projectDir]); var errorOutput = logger.stderrBuffer.toString(); expect(errorOutput, contains('Artificial exception triggered')); expect(errorOutput, contains('try to fix errors in the source code')); expect(errorOutput, contains('re-run with\n--ignore-exceptions')); } test_lifecycle_ignore_errors_disable() async { var projectContents = simpleProject(sourceText: ''' int f() => null '''); var projectDir = createProjectDir(projectContents); await assertRunFailure([projectDir]); var output = logger.stdoutBuffer.toString(); expect(output, contains('1 analysis issue found')); var sep = resourceProvider.pathContext.separator; expect( output, contains("error • Expected to find ';' at lib${sep}test.dart:1:12 • " '(expected_token)')); expect(output, contains('erroneous migration suggestions')); expect(output, contains('We recommend fixing the analysis issues')); expect(output, isNot(contains('Set the lower SDK constraint'))); } test_lifecycle_ignore_errors_enable() async { var projectContents = simpleProject(sourceText: ''' int? f() => null '''); var projectDir = createProjectDir(projectContents); var cli = _createCli(); await runWithPreviewServer(cli, ['--ignore-errors', projectDir], (url) async { var output = logger.stdoutBuffer.toString(); expect(output, isNot(contains('No analysis issues found'))); expect( output, contains('Continuing with migration suggestions due to the use of ' '--ignore-errors.')); await assertPreviewServerResponsive(url); }); } test_lifecycle_import_check_via_export() async { // If the user's code exports a library that imports a non-migrated library, // that's a problem too. var projectContents = simpleProject( sourceText: "export 'package:foo/foo.dart';", packageConfigText: _getPackageConfigText( migrated: false, packagesMigrated: {'foo': true, 'bar': false})); var projectDir = createProjectDir(projectContents); resourceProvider.newFile( packagePath('foo/lib/foo.dart'), "import 'package:bar/bar.dart';"); resourceProvider.newFile(packagePath('bar/lib/bar.dart'), ''); await assertRunFailure([projectDir], expectedExitCode: 1); var output = logger.stdoutBuffer.toString(); expect(output, contains('Error: package has unmigrated dependencies')); // Output should mention bar.dart, since it's unmigrated expect(output, contains('package:bar/bar.dart')); // But it should not mention foo.dart, which is migrated expect(output, isNot(contains('package:foo/foo.dart'))); } test_lifecycle_import_check_via_indirect_export() async { // If the user's code imports a library that exports a library that imports // a non-migrated library, that's a problem too. var projectContents = simpleProject( sourceText: "import 'package:foo/foo.dart';", packageConfigText: _getPackageConfigText( migrated: false, packagesMigrated: {'foo': true, 'bar': true, 'baz': false})); var projectDir = createProjectDir(projectContents); resourceProvider.newFile( packagePath('foo/lib/foo.dart'), "export 'package:bar/bar.dart';"); resourceProvider.newFile( packagePath('bar/lib/bar.dart'), "import 'package:baz/baz.dart';"); resourceProvider.newFile(packagePath('baz/lib/baz.dart'), ''); await assertRunFailure([projectDir], expectedExitCode: 1); var output = logger.stdoutBuffer.toString(); expect(output, contains('Error: package has unmigrated dependencies')); // Output should mention baz.dart, since it's unmigrated expect(output, contains('package:baz/baz.dart')); // But it should not mention foo.dart or bar.dart, which are migrated expect(output, isNot(contains('package:foo/foo.dart'))); expect(output, isNot(contains('package:bar/bar.dart'))); } test_lifecycle_import_lib_from_test() async { Map makeProject({bool migrated = false}) { return simpleProject(migrated: migrated) ..['test/foo.dart'] = ''' import '../lib/test.dart'; '''; } var projectContents = makeProject(); var projectDir = createProjectDir(projectContents); var cli = _createCli(); var cliRunner = cli.decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); bool applyHookCalled = false; cli._onApplyHook = () { expect(applyHookCalled, false); applyHookCalled = true; // Changes should have been made assertProjectContents(projectDir, makeProject(migrated: true)); }; await cliRunner.run(); assertNormalExit(cliRunner); expect(applyHookCalled, true); } @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/44118') test_lifecycle_issue_44118() async { var projectContents = simpleProject(sourceText: ''' int f() => null '''); projectContents['lib/foo.dart'] = ''' import 'test.dart'; '''; var projectDir = createProjectDir(projectContents); await assertRunFailure([projectDir]); var output = logger.stdoutBuffer.toString(); expect(output, contains('1 analysis issue found')); var sep = resourceProvider.pathContext.separator; expect( output, contains("error • Expected to find ';' at lib${sep}test.dart:1:12 • " '(expected_token)')); expect( output, contains( 'analysis errors will result in erroneous migration suggestions')); expect(output, contains('Please fix the analysis issues')); } test_lifecycle_migration_already_performed() async { var projectContents = simpleProject(migrated: true); var projectDir = createProjectDir(projectContents); await assertRunFailure([projectDir], expectedExitCode: 0); var output = logger.stdoutBuffer.toString(); expect(output, contains('All sources appear to be already migrated. Nothing to do.')); } test_lifecycle_no_preview() async { var projectContents = simpleProject(); var projectDir = createProjectDir(projectContents); var cliRunner = _createCli() .decodeCommandLineArgs(_parseArgs(['--no-web-preview', projectDir])); await cliRunner.run(); assertNormalExit(cliRunner); // Check that a summary was printed var output = logger.stdoutBuffer.toString(); expect(output, contains('Diff of changes')); // And that it refers to test.dart and pubspec.yaml expect(output, contains('test.dart')); expect(output, contains('pubspec.yaml')); // And that it contains text from a changed line expect(output, contains('f() => null')); // And that it tells the user they can rerun with `--apply-changes` expect(output, contains('--apply-changes')); // No changes should have been made assertProjectContents(projectDir, projectContents); } test_lifecycle_override_paths() async { Map makeProject({bool migrated = false}) { var projectContents = simpleProject(migrated: migrated); projectContents['lib/test.dart'] = ''' import 'skip.dart'; import 'analyze_but_do_not_migrate.dart'; void f(int x) {} void g(int${migrated ? '?' : ''} x) {} void h(int${migrated ? '?' : ''} x) {} void call_h() => h(null); '''; projectContents['lib/skip.dart'] = ''' import 'test.dart'; void call_f() => f(null); '''; projectContents['lib/analyze_but_do_not_migrate.dart'] = ''' import 'test.dart'; void call_g() => g(null); '''; return projectContents; } var projectContents = makeProject(); var projectDir = createProjectDir(projectContents); var testPath = resourceProvider.pathContext.join(projectDir, 'lib', 'test.dart'); var analyzeButDoNotMigratePath = resourceProvider.pathContext .join(projectDir, 'lib', 'analyze_but_do_not_migrate.dart'); overridePathsToProcess = {testPath, analyzeButDoNotMigratePath}; overrideShouldBeMigrated = (path) => path == testPath; var cliRunner = _createCli().decodeCommandLineArgs(_parseArgs([ '--no-web-preview', '--apply-changes', '--skip-import-check', projectDir ])); await cliRunner.run(); assertNormalExit(cliRunner); // Check that a summary was printed expect(logger.stdoutBuffer.toString(), contains('Applying changes')); // And that it refers to test.dart and pubspec.yaml expect(logger.stdoutBuffer.toString(), contains('test.dart')); expect(logger.stdoutBuffer.toString(), contains('pubspec.yaml')); // And that it does not tell the user they can rerun with `--apply-changes` expect(logger.stdoutBuffer.toString(), isNot(contains('--apply-changes'))); // Changes should have been made only to test.dart, and only accounting for // the calls coming from analyze_but_do_not_migrate.dart and test.dart assertProjectContents(projectDir, makeProject(migrated: true)); } test_lifecycle_preview() async { var projectContents = simpleProject(); var projectDir = createProjectDir(projectContents); var cli = _createCli(); await runWithPreviewServer(cli, [projectDir], (url) async { var localhostAddressText = Platform.environment.containsKey('FORCE_IPV6') ? '[::1]' : '127.0.0.1'; expect( logger.stdoutBuffer.toString(), contains('No analysis issues found')); expect(url, startsWith('http://$localhostAddressText:')); await assertPreviewServerResponsive(url); }); // No changes should have been made. assertProjectContents(projectDir, projectContents); } test_lifecycle_preview_add_hint() async { var projectContents = simpleProject(sourceText: 'int x;'); var projectDir = createProjectDir(projectContents); var cli = _createCli(); await runWithPreviewServer(cli, [projectDir], (url) async { expect( logger.stdoutBuffer.toString(), contains('No analysis issues found')); await assertPreviewServerResponsive(url); var uri = Uri.parse(url); var authToken = uri.queryParameters['authToken']; var response = await httpPost( uri.replace( path: resourceProvider.pathContext .toUri(resourceProvider.pathContext .join(projectDir, 'lib', 'test.dart')) .path, queryParameters: { 'offset': '3', 'end': '3', 'replacement': '/*!*/', 'authToken': authToken }), headers: {'Content-Type': 'application/json; charset=UTF-8'}); assertHttpSuccess(response); assertProjectContents( projectDir, simpleProject(sourceText: 'int/*!*/ x;')); }); } test_lifecycle_preview_apply_changes() async { var projectContents = simpleProject(); var projectDir = createProjectDir(projectContents); var cli = _createCli(); bool applyHookCalled = false; cli._onApplyHook = () { expect(applyHookCalled, false); applyHookCalled = true; // Changes should have been made assertProjectContents(projectDir, simpleProject(migrated: true)); }; await runWithPreviewServer(cli, [projectDir], (url) async { expect( logger.stdoutBuffer.toString(), contains('No analysis issues found')); await assertPreviewServerResponsive(url); await _tellPreviewToApplyChanges(url); expect(applyHookCalled, true); }); } test_lifecycle_preview_apply_changes_unreferenced_part() async { var projectContents = simpleProject() ..['lib/unreferenced_part.dart'] = ''' part of foo; int f() => null; '''; var projectDir = createProjectDir(projectContents); var cli = _createCli(); bool applyHookCalled = false; cli._onApplyHook = () { expect(applyHookCalled, false); applyHookCalled = true; // Changes should have been made assertProjectContents(projectDir, simpleProject(migrated: true)); }; await runWithPreviewServer(cli, [projectDir], (url) async { expect( logger.stdoutBuffer.toString(), contains('No analysis issues found')); await assertPreviewServerResponsive(url); await _tellPreviewToApplyChanges(url); expect(applyHookCalled, true); }); } test_lifecycle_preview_extra_forward_slash() async { var projectDir = createProjectDir(simpleProject()); var cli = _createCli(); await runWithPreviewServer(cli, [projectDir], (url) async { var uri = Uri.parse(url); await assertPreviewServerResponsive( uri.replace(path: uri.path + '/').toString()); }); } test_lifecycle_preview_navigation_links() async { var projectContents = simpleProject(sourceText: 'int x;'); projectContents['lib/src/test.dart'] = 'import "../test.dart"; int y = x;'; var projectDir = createProjectDir(projectContents); var cli = _createCli(); await runWithPreviewServer(cli, [projectDir], (url) async { expect( logger.stdoutBuffer.toString(), contains('No analysis issues found')); await assertPreviewServerResponsive(url); final uri = Uri.parse(url); final authToken = uri.queryParameters['authToken']; final fileResponse = await httpGet( uri.replace( path: resourceProvider.pathContext .toUri(resourceProvider.pathContext .join(projectDir, 'lib', 'src', 'test.dart')) .path, queryParameters: {'inline': 'true', 'authToken': authToken}), headers: {'Content-Type': 'application/json; charset=UTF-8'}); final fileJson = FileDetails.fromJson(jsonDecode(fileResponse.body)); final navigation = fileJson.navigationContent; final aLink = RegExp(r''); for (final match in aLink.allMatches(navigation)) { var href = match.group(1); final contentsResponse = await httpGet( uri.replace( path: Uri.parse(href).path, queryParameters: {'inline': 'true', 'authToken': authToken}), headers: {'Content-Type': 'application/json; charset=UTF-8'}); assertHttpSuccess(contentsResponse); } }); } test_lifecycle_preview_navigation_tree() async { var projectContents = simpleProject(sourceText: 'int x;'); var projectDir = createProjectDir(projectContents); var cli = _createCli(); await runWithPreviewServer(cli, [projectDir], (url) async { expect( logger.stdoutBuffer.toString(), contains('No analysis issues found')); await assertPreviewServerResponsive(url); var uri = Uri.parse(url); var authToken = uri.queryParameters['authToken']; var treeResponse = await httpGet( uri.replace( path: '/_preview/navigationTree.json', queryParameters: {'authToken': authToken}), headers: {'Content-Type': 'application/json; charset=UTF-8'}); var navRoots = jsonDecode(treeResponse.body); for (final root in navRoots) { var navTree = NavigationTreeNode.fromJson(root) as NavigationTreeDirectoryNode; for (final file in navTree.subtree) { if (file is NavigationTreeFileNode) { final contentsResponse = await httpGet( uri .resolve(file.href) .replace(queryParameters: {'authToken': authToken}), headers: {'Content-Type': 'application/json; charset=UTF-8'}); assertHttpSuccess(contentsResponse); } } } }); } test_lifecycle_preview_on_host_any() async { var projectContents = simpleProject(); var projectDir = createProjectDir(projectContents); var cli = _createCli() ..decodeCommandLineArgs(_parseArgs(['--preview-hostname=any'])); await runWithPreviewServer(cli, [projectDir], (url) async { expect(url, isNot(contains('localhost'))); await assertPreviewServerResponsive(url); }); // No changes should have been made. assertProjectContents(projectDir, projectContents); } test_lifecycle_preview_region_link() async { var projectContents = simpleProject(sourceText: 'int x;'); var projectDir = createProjectDir(projectContents); var cli = _createCli(); await runWithPreviewServer(cli, [projectDir], (url) async { expect( logger.stdoutBuffer.toString(), contains('No analysis issues found')); await assertPreviewServerResponsive(url); var uri = Uri.parse(url); var authToken = uri.queryParameters['authToken']; var regionResponse = await httpGet( uri.replace( path: resourceProvider.pathContext .toUri(resourceProvider.pathContext .join(projectDir, 'lib', 'test.dart')) .path, queryParameters: { 'region': 'region', 'offset': '3', 'authToken': authToken }), headers: {'Content-Type': 'application/json; charset=UTF-8'}); var regionJson = EditDetails.fromJson(jsonDecode(regionResponse.body)); final displayPath = regionJson.displayPath; final uriPath = regionJson.uriPath; // uriPath should be a working URI final contentsResponse = await httpGet( uri.replace( path: uriPath, queryParameters: {'inline': 'true', 'authToken': authToken}), headers: {'Content-Type': 'application/json; charset=UTF-8'}); assertHttpSuccess(contentsResponse); // Display path should be the actual windows path final file = resourceProvider .getFolder(projectDir) .getChildAssumingFile(displayPath); expect(file.exists, isTrue); }); } test_lifecycle_preview_region_table_path() async { var projectContents = simpleProject(sourceText: 'int x;'); var projectDir = createProjectDir(projectContents); var cli = _createCli(); await runWithPreviewServer(cli, [projectDir], (url) async { expect( logger.stdoutBuffer.toString(), contains('No analysis issues found')); await assertPreviewServerResponsive(url); final uri = Uri.parse(url); final authToken = uri.queryParameters['authToken']; final fileResponse = await httpGet( uri.replace( path: resourceProvider.pathContext .toUri(resourceProvider.pathContext .join(projectDir, 'lib', 'test.dart')) .path, queryParameters: {'inline': 'true', 'authToken': authToken}), headers: {'Content-Type': 'application/json; charset=UTF-8'}); final fileJson = FileDetails.fromJson(jsonDecode(fileResponse.body)); final regions = fileJson.regions; final regionsPathRegex = RegExp(r''); expect(regionsPathRegex.hasMatch(regions), true); final regionsPath = regionsPathRegex.matchAsPrefix(regions).group(1); final contentsResponse = await httpGet( uri.replace( path: Uri.parse(regionsPath).path, queryParameters: {'inline': 'true', 'authToken': authToken}), headers: {'Content-Type': 'application/json; charset=UTF-8'}); assertHttpSuccess(contentsResponse); }); } test_lifecycle_preview_rerun() async { var origSourceText = 'void f() {}'; var projectContents = simpleProject(sourceText: origSourceText); var projectDir = createProjectDir(projectContents); var cli = _createCli(); await runWithPreviewServer(cli, [projectDir], (url) async { await assertPreviewServerResponsive(url); var uri = Uri.parse(url); var testPath = resourceProvider.pathContext.join(projectDir, 'lib', 'test.dart'); var newSourceText = 'void g() {}'; resourceProvider.getFile(testPath).writeAsStringSync(newSourceText); // We haven't rerun, so getting the file details from the server should // still yield the original source text expect(await getSourceFromServer(uri, testPath), origSourceText); var response = await httpPost(uri.replace(path: 'rerun-migration'), headers: {'Content-Type': 'application/json; charset=UTF-8'}); assertHttpSuccess(response); // Now that we've rerun, the server should yield the new source text expect(await getSourceFromServer(uri, testPath), newSourceText); }); } test_lifecycle_preview_rerun_added_file() async { var projectContents = simpleProject(); var projectDir = createProjectDir(projectContents); var cli = _createCli(); await runWithPreviewServer(cli, [projectDir], (url) async { await assertPreviewServerResponsive(url); var uri = Uri.parse(url); var test2Path = resourceProvider.pathContext.join(projectDir, 'lib', 'test2.dart'); var newSourceText = 'void g() {}'; resourceProvider.getFile(test2Path).writeAsStringSync(newSourceText); // We haven't rerun, so getting the file details from the server should // fail var response = await tryGetSourceFromServer(uri, test2Path); expect(response.statusCode, 404); response = await httpPost(uri.replace(path: 'rerun-migration'), headers: {'Content-Type': 'application/json; charset=UTF-8'}); assertHttpSuccess(response); // Now that we've rerun, the server should yield the new source text expect(await getSourceFromServer(uri, test2Path), newSourceText); }); } test_lifecycle_preview_rerun_deleted_file() async { var projectContents = {...simpleProject(), 'lib/other.dart': ''}; var projectDir = createProjectDir(projectContents); var cli = _createCli(); // Note: we use the summary to verify that the deletion was noticed var summaryPath = resourceProvider.convertPath('/summary.json'); await runWithPreviewServer(cli, ['--summary', summaryPath, projectDir], (url) async { await assertPreviewServerResponsive(url); // lib/test.dart should be readable from the server and appear in the // summary var uri = Uri.parse(url); var testPath = resourceProvider.pathContext.join(projectDir, 'lib', 'test.dart'); await getSourceFromServer(uri, testPath); var summaryData = jsonDecode(resourceProvider.getFile(summaryPath).readAsStringSync()); var separator = resourceProvider.pathContext.separator; expect(summaryData['changes']['byPath'], contains('lib${separator}test.dart')); // Now delete the lib file and rerun resourceProvider.deleteFile(testPath); var response = await httpPost(uri.replace(path: 'rerun-migration'), headers: {'Content-Type': 'application/json; charset=UTF-8'}); assertHttpSuccess(response); // lib/test.dart should no longer be readable from the server and // should no longer appear in the summary response = await tryGetSourceFromServer(uri, testPath); expect(response.statusCode, 404); summaryData = jsonDecode(resourceProvider.getFile(summaryPath).readAsStringSync()); expect(summaryData['changes']['byPath'], isNot(contains('lib${separator}test.dart'))); }); } test_lifecycle_preview_rerun_with_ignore_errors() async { var origSourceText = 'void f(int i) {}'; var projectContents = simpleProject(sourceText: origSourceText); var projectDir = createProjectDir(projectContents); var cli = _createCli(); await runWithPreviewServer(cli, ['--ignore-errors', projectDir], (url) async { await assertPreviewServerResponsive(url); var uri = Uri.parse(url); var testPath = resourceProvider.pathContext.join(projectDir, 'lib', 'test.dart'); resourceProvider.getFile(testPath).writeAsStringSync('void f(int? i) {}'); // We haven't rerun, so getting the file details from the server should // still yield the original source text, with informational space. expect(await getSourceFromServer(uri, testPath), 'void f(int i) {}'); var response = await httpPost(uri.replace(path: 'rerun-migration'), headers: {'Content-Type': 'application/json; charset=UTF-8'}); assertHttpSuccess(response); var body = jsonDecode(response.body); expect(body['success'], isTrue); expect(body['errors'], isNull); // Now that we've rerun, the server should yield the new source text expect(await getSourceFromServer(uri, testPath), 'void f(int? i) {}'); }); } test_lifecycle_preview_rerun_with_new_analysis_errors() async { var origSourceText = 'void f(int i) {}'; var projectContents = simpleProject(sourceText: origSourceText); var projectDir = createProjectDir(projectContents); var cli = _createCli(); await runWithPreviewServer(cli, [projectDir], (url) async { await assertPreviewServerResponsive(url); var uri = Uri.parse(url); var testPath = resourceProvider.pathContext.join(projectDir, 'lib', 'test.dart'); var newSourceText = 'void f(int? i) {}'; resourceProvider.getFile(testPath).writeAsStringSync(newSourceText); // We haven't rerun, so getting the file details from the server should // still yield the original source text, with informational space. expect(await getSourceFromServer(uri, testPath), 'void f(int i) {}'); var response = await httpPost(uri.replace(path: 'rerun-migration'), headers: {'Content-Type': 'application/json; charset=UTF-8'}); assertHttpSuccess(response); var body = jsonDecode(response.body); expect(body['success'], isFalse); expect(body['errors'], hasLength(1)); var error = body['errors'].single; expect(error['severity'], equals('error')); expect( error['message'], equals( "This requires the 'non-nullable' language feature to be enabled")); expect(error['location'], equals(resourceProvider.pathContext.join('lib', 'test.dart:1:11'))); expect(error['code'], equals('experiment_not_enabled')); }); } test_lifecycle_preview_serves_only_from_project_dir() async { var crazyFunctionName = 'crazyFunctionNameThatHasNeverBeenSeenBefore'; var projectContents = simpleProject(sourceText: 'void $crazyFunctionName() {}'); var mainProjectDir = createProjectDir(projectContents); var otherProjectDir = createProjectDir(projectContents, posixPath: '/other_project_dir'); var cli = _createCli(); await runWithPreviewServer(cli, [mainProjectDir], (url) async { await assertPreviewServerResponsive(url); var uri = Uri.parse(url); Future tryGetSourceFromProject(String projectDir) => tryGetSourceFromServer( uri, resourceProvider.pathContext .join(projectDir, 'lib', 'test.dart')); // To verify that we're forming the request correctly, make sure that we // can read a file from mainProjectDir. var response = await tryGetSourceFromProject(mainProjectDir); assertHttpSuccess(response); // And that crazyFunctionName appears in the response expect(response.body, contains(crazyFunctionName)); // Now verify that making the exact same request from otherProjectDir // fails. response = await tryGetSourceFromProject(otherProjectDir); expect(response.statusCode, 404); // And check that we didn't leak any info through the 404 response. expect(response.body, isNot(contains(crazyFunctionName))); }); } test_lifecycle_preview_stack_hint_action() async { var projectContents = simpleProject(sourceText: 'int x;'); var projectDir = createProjectDir(projectContents); var cli = _createCli(); await runWithPreviewServer(cli, [projectDir], (url) async { expect( logger.stdoutBuffer.toString(), contains('No analysis issues found')); await assertPreviewServerResponsive(url); var uri = Uri.parse(url); var authToken = uri.queryParameters['authToken']; var regionResponse = await httpGet( uri.replace( path: resourceProvider.pathContext .toUri(resourceProvider.pathContext .join(projectDir, 'lib', 'test.dart')) .path, queryParameters: { 'region': 'region', 'offset': '3', 'authToken': authToken }), headers: {'Content-Type': 'application/json; charset=UTF-8'}); var regionJson = jsonDecode(regionResponse.body); var response = await httpPost( uri.replace( path: 'apply-hint', queryParameters: {'authToken': authToken}), headers: {'Content-Type': 'application/json; charset=UTF-8'}, body: jsonEncode( regionJson['traces'][0]['entries'][0]['hintActions'][0])); assertHttpSuccess(response); assertProjectContents( projectDir, simpleProject(sourceText: 'int/*?*/ x;')); }); } test_lifecycle_preview_stacktrace_link() async { var projectContents = simpleProject(sourceText: 'int x;'); var projectDir = createProjectDir(projectContents); var cli = _createCli(); await runWithPreviewServer(cli, [projectDir], (url) async { expect( logger.stdoutBuffer.toString(), contains('No analysis issues found')); await assertPreviewServerResponsive(url); var uri = Uri.parse(url); var authToken = uri.queryParameters['authToken']; var regionUri = uri.replace( path: resourceProvider.pathContext .toUri(resourceProvider.pathContext .join(projectDir, 'lib', 'test.dart')) .path, queryParameters: { 'region': 'region', 'offset': '3', 'authToken': authToken }); var regionResponse = await httpGet(regionUri, headers: {'Content-Type': 'application/json; charset=UTF-8'}); var regionJson = EditDetails.fromJson(jsonDecode(regionResponse.body)); final traceEntry = regionJson.traces[0].entries[0]; final uriPath = traceEntry.link.href; // uriPath should be a working URI final contentsResponse = await httpGet( regionUri .resolve(uriPath) .replace(queryParameters: {'authToken': authToken}), headers: {'Content-Type': 'application/json; charset=UTF-8'}); assertHttpSuccess(contentsResponse); }); } test_lifecycle_skip_import_check_disable() async { var projectContents = simpleProject( sourceText: ''' import 'package:foo/foo.dart'; import 'package:foo/bar.dart'; int f() => null; ''', packageConfigText: _getPackageConfigText( migrated: false, packagesMigrated: {'foo': false})); var projectDir = createProjectDir(projectContents); resourceProvider.newFile(packagePath('foo/lib/foo.dart'), ''); resourceProvider.newFile(packagePath('foo/lib/bar.dart'), ''); await assertRunFailure([projectDir], expectedExitCode: 1); var output = logger.stdoutBuffer.toString(); expect(output, contains('Error: package has unmigrated dependencies')); // Output should contain an indented, sorted list of all unmigrated // dependencies. expect( output, contains('\n package:foo/bar.dart\n package:foo/foo.dart')); // Output should mention that the user can rerun with `--skip-import-check`. expect(output, contains('`--${CommandLineOptions.skipImportCheckFlag}`')); } test_lifecycle_skip_import_check_enable() async { var projectContents = simpleProject( sourceText: ''' import 'package:foo/foo.dart'; import 'package:foo/bar.dart'; int f() => null; ''', packageConfigText: _getPackageConfigText( migrated: false, packagesMigrated: {'foo': false})); var projectDir = createProjectDir(projectContents); resourceProvider.newFile(packagePath('foo/lib/foo.dart'), ''); resourceProvider.newFile(packagePath('foo/lib/bar.dart'), ''); var cli = _createCli(); await runWithPreviewServer(cli, ['--skip-import-check', projectDir], (url) async { await assertPreviewServerResponsive(url); var output = logger.stdoutBuffer.toString(); expect(output, contains('Warning: package has unmigrated dependencies')); // Output should not mention the particular unmigrated dependencies. expect(output, isNot(contains('package:foo'))); // Output should mention that the user can rerun without // `--skip-import-check`. expect(output, contains('`--${CommandLineOptions.skipImportCheckFlag}`')); }); } test_lifecycle_summary() async { var projectContents = simpleProject(); var projectDir = createProjectDir(projectContents); var summaryPath = resourceProvider.convertPath('/summary.json'); var cliRunner = _createCli().decodeCommandLineArgs( _parseArgs(['--no-web-preview', '--summary', summaryPath, projectDir])); await cliRunner.run(); var summaryData = jsonDecode(resourceProvider.getFile(summaryPath).readAsStringSync()); expect(summaryData, TypeMatcher()); expect(summaryData, contains('changes')); assertNormalExit(cliRunner); } test_lifecycle_summary_does_not_double_count_hint_removals() async { var projectContents = simpleProject(sourceText: 'int/*?*/ x;'); var projectDir = createProjectDir(projectContents); var summaryPath = resourceProvider.convertPath('/summary.json'); var cliRunner = _createCli().decodeCommandLineArgs( _parseArgs(['--no-web-preview', '--summary', summaryPath, projectDir])); await cliRunner.run(); assertNormalExit(cliRunner); var summaryData = jsonDecode(resourceProvider.getFile(summaryPath).readAsStringSync()); var separator = resourceProvider.pathContext.separator; expect(summaryData['changes']['byPath']['lib${separator}test.dart'], {'makeTypeNullableDueToHint': 1}); } test_lifecycle_summary_rewritten_upon_rerun() async { var projectContents = simpleProject(sourceText: 'int f(int/*?*/ i) => i;'); var projectDir = createProjectDir(projectContents); var cli = _createCli(); var summaryPath = resourceProvider.convertPath('/summary.json'); await runWithPreviewServer(cli, ['--summary', summaryPath, projectDir], (url) async { await assertPreviewServerResponsive(url); var summaryData = jsonDecode(resourceProvider.getFile(summaryPath).readAsStringSync()); var separator = resourceProvider.pathContext.separator; expect(summaryData['changes']['byPath']['lib${separator}test.dart'], {'makeTypeNullableDueToHint': 1, 'makeTypeNullable': 1}); var testPath = resourceProvider.pathContext.join(projectDir, 'lib', 'test.dart'); var newSourceText = 'int f(int/*?*/ i) => i + 1;'; resourceProvider.getFile(testPath).writeAsStringSync(newSourceText); // Rerunning should create a new summary var uri = Uri.parse(url); var response = await httpPost(uri.replace(path: 'rerun-migration'), headers: {'Content-Type': 'application/json; charset=UTF-8'}); assertHttpSuccess(response); summaryData = jsonDecode(resourceProvider.getFile(summaryPath).readAsStringSync()); expect(summaryData['changes']['byPath']['lib${separator}test.dart'], { 'typeNotMadeNullable': 1, 'makeTypeNullableDueToHint': 1, 'checkExpression': 1 }); }); } test_lifecycle_uri_error() async { var projectContents = simpleProject(sourceText: ''' import 'package:does_not/exist.dart'; int f() => null; '''); var projectDir = createProjectDir(projectContents); await assertRunFailure([projectDir]); var output = logger.stdoutBuffer.toString(); expect(output, contains('1 analysis issue found')); expect(output, contains('uri_does_not_exist')); expect(output, isNot(contains('erroneous migration suggestions'))); expect(output, contains('Run `dart pub get`')); expect(output, contains('Try running `dart migrate` again')); expect(output, isNot(contains('Set the lower SDK constraint'))); } test_migrate_path_absolute() { resourceProvider.newFolder(resourceProvider.pathContext .join(resourceProvider.pathContext.current, 'foo')); expect( resourceProvider.pathContext .isAbsolute(assertParseArgsSuccess(['foo']).directory), isTrue); } test_migrate_path_file() { resourceProvider.newFile(resourceProvider.pathContext.absolute('foo'), ''); expect(assertDecodeArgsFailure(['foo']), contains('foo is a file')); } test_migrate_path_non_existent() { expect(assertDecodeArgsFailure(['foo']), contains('foo does not exist')); } test_migrate_path_none() { expect(assertParseArgsSuccess([]).directory, resourceProvider.pathContext.current); } test_migrate_path_normalized() { expect(assertParseArgsSuccess(['foo/..']).directory, isNot(contains('..'))); } test_migrate_path_one() { resourceProvider.newFolder(resourceProvider.pathContext .join(resourceProvider.pathContext.current, 'foo')); expect( assertParseArgsSuccess(['foo']).directory, resourceProvider.pathContext .join(resourceProvider.pathContext.current, 'foo')); } test_migrate_path_two() async { var stderrText = await assertRunFailure(['foo', 'bar'], withUsage: true); expect(stderrText, contains('No more than one path may be specified')); } test_option_preview_hostname() { expect( assertParseArgsSuccess(['--preview-hostname', 'any']).previewHostname, 'any'); } test_option_preview_hostname_default() { expect(assertParseArgsSuccess([]).previewHostname, 'localhost'); } test_option_preview_port() { expect( assertParseArgsSuccess(['--preview-port', '4040']).previewPort, 4040); } test_option_preview_port_default() { expect(assertParseArgsSuccess([]).previewPort, isNull); } test_option_preview_port_format_error() { expect(assertDecodeArgsFailure(['--preview-port', 'abc']), contains('Invalid value for --preview-port')); } test_option_sdk() { var path = Uri.parse('file:///foo/bar/baz').toFilePath(); expect(assertParseArgsSuccess(['--sdk-path', path]).sdkPath, same(path)); } test_option_sdk_default() { var cli = MigrationCli(binaryName: 'nnbd_migration'); var cliRunner = cli.decodeCommandLineArgs(_parseArgs([])); expect(Directory(path.join(cliRunner.options.sdkPath, 'bin')).existsSync(), isTrue); } test_option_sdk_hidden() { var optionName = '--sdk-path'; expect(_getHelpText(verbose: false), isNot(contains(optionName))); expect(_getHelpText(verbose: true), contains(optionName)); } test_option_summary() { var summaryPath = resourceProvider.convertPath('/summary.json'); expect(assertParseArgsSuccess(['--summary', summaryPath]).summary, summaryPath); } test_option_unrecognized() async { expect( await assertParseArgsFailure(['--this-option-does-not-exist']), contains( 'Could not find an option named "this-option-does-not-exist"')); } test_package_config_does_not_exist() async { var projectContents = simpleProject() ..remove('.dart_tool/package_config.json'); var projectDir = createProjectDir(projectContents); if (dartVersionIsNullSafeByDefault) { // The lack of a package config file means the test file is already opted // in. await assertRunFailure(['--apply-changes', projectDir]); _expectErrorIndicatingCodeIsAlreadyOptedIn(); } else { var cliRunner = _createCli() .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); // The Dart source code should still be migrated. assertProjectContents( projectDir, simpleProject(migrated: true) ..remove('.dart_tool/package_config.json')); } } test_package_config_is_missing_languageVersion() async { var packageConfigText = ''' { "configVersion": 2, "packages": [ { "name": "test", "rootUri": "../", "packageUri": "lib/" } ] } '''; var projectContents = simpleProject(packageConfigText: packageConfigText); var projectDir = createProjectDir(projectContents); if (dartVersionIsNullSafeByDefault) { // An omitted languageVersion field means the code is opted in to null // safety. await assertRunFailure(['--apply-changes', projectDir]); _expectErrorIndicatingCodeIsAlreadyOptedIn(); } else { var cliRunner = _createCli() .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); // The Dart source code should still be migrated. assertProjectContents(projectDir, simpleProject(migrated: true, packageConfigText: packageConfigText)); } } test_package_config_is_missing_this_package() async { var packageConfigText = ''' { "configVersion": 2, "packages": [ ] } '''; var projectContents = simpleProject(packageConfigText: packageConfigText); var projectDir = createProjectDir(projectContents); if (dartVersionIsNullSafeByDefault) { // An omitted entry in the package config means the code is opted in to null // safety. await assertRunFailure(['--apply-changes', projectDir]); _expectErrorIndicatingCodeIsAlreadyOptedIn(); } else { var cliRunner = _createCli() .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); // The Dart source code should still be migrated. assertProjectContents(projectDir, simpleProject(migrated: true, packageConfigText: packageConfigText)); } } test_package_config_is_wrong_version() async { var packageConfigText = ''' { "configVersion": 3, "packages": [ { "name": "test", "rootUri": "../", "packageUri": "lib/", "languageVersion": "2.6" } ] } '''; var projectContents = simpleProject(packageConfigText: packageConfigText); var projectDir = createProjectDir(projectContents); if (dartVersionIsNullSafeByDefault) { // An unreadable package config means the code is opted in to null safety. await assertRunFailure(['--apply-changes', projectDir]); _expectErrorIndicatingCodeIsAlreadyOptedIn(); } else { var cliRunner = _createCli() .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); // The Dart source code should still be migrated. assertProjectContents(projectDir, simpleProject(migrated: true, packageConfigText: packageConfigText)); } } test_pubspec_add_collection_dependency() async { var projectContents = simpleProject(sourceText: ''' int firstEven(Iterable x) => x.firstWhere((x) => x.isEven, orElse: () => null); ''', pubspecText: ''' name: test environment: sdk: '>=2.6.0 <3.0.0' dependencies: foo: ^1.2.3 '''); var projectDir = createProjectDir(projectContents); var cliRunner = _createCli() .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); expect( logger.stdoutBuffer.toString(), contains('Please run `dart pub get`')); // The Dart source code should still be migrated. assertProjectContents( projectDir, simpleProject(migrated: true, sourceText: ''' import 'package:collection/collection.dart' show IterableExtension; int? firstEven(Iterable x) => x.firstWhereOrNull((x) => x.isEven); ''', pubspecText: ''' name: test environment: sdk: '>=2.12.0 <3.0.0' dependencies: foo: ^1.2.3 collection: ^1.15.0-nullsafety.4 ''')); } test_pubspec_add_dependency_and_environment_sections() async { var projectContents = simpleProject(sourceText: ''' int firstEven(Iterable x) => x.firstWhere((x) => x.isEven, orElse: () => null); ''', pubspecText: ''' name: test '''); var projectDir = createProjectDir(projectContents); var cliRunner = _createCli() .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); expect( logger.stdoutBuffer.toString(), contains('Please run `dart pub get`')); // The Dart source code should still be migrated. assertProjectContents( projectDir, simpleProject(migrated: true, sourceText: ''' import 'package:collection/collection.dart' show IterableExtension; int? firstEven(Iterable x) => x.firstWhereOrNull((x) => x.isEven); ''', // Note: section order is weird, but it's valid and this is a rare use // case. pubspecText: ''' environment: sdk: '>=2.12.0 <3.0.0' dependencies: collection: ^1.15.0-nullsafety.4 name: test ''')); } test_pubspec_add_dependency_section() async { var projectContents = simpleProject(sourceText: ''' int firstEven(Iterable x) => x.firstWhere((x) => x.isEven, orElse: () => null); '''); var projectDir = createProjectDir(projectContents); var cliRunner = _createCli() .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); expect( logger.stdoutBuffer.toString(), contains('Please run `dart pub get`')); // The Dart source code should still be migrated. assertProjectContents(projectDir, simpleProject(migrated: true, sourceText: ''' import 'package:collection/collection.dart' show IterableExtension; int? firstEven(Iterable x) => x.firstWhereOrNull((x) => x.isEven); ''', // Note: `dependencies` section is in a weird place, but it's valid and // this is a rare use case. pubspecText: ''' dependencies: collection: ^1.15.0-nullsafety.4 name: test environment: sdk: '>=2.12.0 <3.0.0' ''')); } test_pubspec_does_not_exist() async { var projectContents = simpleProject()..remove('pubspec.yaml'); var projectDir = createProjectDir(projectContents); var cliRunner = _createCli() .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); // The Dart source code should still be migrated. assertProjectContents( projectDir, simpleProject( migrated: true, // The package config file should not have been touched. packageConfigText: _getPackageConfigText(migrated: false)) ..remove('pubspec.yaml')); } test_pubspec_environment_is_missing_sdk() async { var projectContents = simpleProject(pubspecText: ''' name: test environment: foo: 1 '''); var projectDir = createProjectDir(projectContents); var cliRunner = _createCli() .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); // The Dart source code should still be migrated. assertProjectContents( projectDir, simpleProject(migrated: true, pubspecText: ''' name: test environment: foo: 1 sdk: '>=2.12.0 <3.0.0' ''')); } test_pubspec_environment_is_not_a_map() async { var pubspecText = ''' name: test environment: 1 '''; var projectContents = simpleProject(pubspecText: pubspecText); var projectDir = createProjectDir(projectContents); var cliRunner = _createCli() .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); // The Dart source code should still be migrated. assertProjectContents( projectDir, simpleProject(migrated: true, pubspecText: pubspecText)); } test_pubspec_environment_sdk_is_exact_version() async { var pubspecText = ''' name: test environment: sdk: '2.0.0' '''; var projectContents = simpleProject(pubspecText: pubspecText); var projectDir = createProjectDir(projectContents); var cliRunner = _createCli() .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); // The Dart source code should still be migrated. assertProjectContents( projectDir, simpleProject( migrated: true, pubspecText: pubspecText, // The package config file should not have been touched. packageConfigText: _getPackageConfigText(migrated: false))); } test_pubspec_environment_sdk_is_missing_min() async { var pubspecText = ''' name: test environment: sdk: '<3.0.0' '''; var projectContents = simpleProject(pubspecText: pubspecText); var projectDir = createProjectDir(projectContents); var cliRunner = _createCli() .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); // The Dart source code should still be migrated. assertProjectContents( projectDir, simpleProject( migrated: true, pubspecText: pubspecText, // The package config file should not have been touched. packageConfigText: _getPackageConfigText(migrated: false))); } test_pubspec_environment_sdk_is_not_string() async { var pubspecText = ''' name: test environment: sdk: 1 '''; var projectContents = simpleProject(pubspecText: pubspecText); var projectDir = createProjectDir(projectContents); var cliRunner = _createCli() .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); // The Dart source code should still be migrated. assertProjectContents( projectDir, simpleProject( migrated: true, pubspecText: pubspecText, // The package config file should not have been touched. packageConfigText: _getPackageConfigText(migrated: false))); } test_pubspec_environment_sdk_is_union() async { var pubspecText = ''' name: test environment: sdk: '>=2.0.0 <2.1.0 >=2.2.0 <3.0.0' '''; var projectContents = simpleProject(pubspecText: pubspecText); var projectDir = createProjectDir(projectContents); var cliRunner = _createCli() .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); // The Dart source code should still be migrated. assertProjectContents( projectDir, simpleProject( migrated: true, pubspecText: pubspecText, // The package config file should not have been touched. packageConfigText: _getPackageConfigText(migrated: false))); } test_pubspec_has_unusual_max_sdk_constraint() async { // No one should be using a weird max SDK constraint like this. If they are // doing so, we'll fix it to 3.0.0. var projectContents = simpleProject(pubspecText: ''' name: test environment: sdk: '>=2.6.0 <2.17.4' '''); var projectDir = createProjectDir(projectContents); var cliRunner = _createCli() .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); // The Dart source code should still be migrated. assertProjectContents( projectDir, simpleProject(migrated: true, pubspecText: ''' name: test environment: sdk: '>=2.12.0 <3.0.0' ''')); } test_pubspec_is_missing_environment() async { var projectContents = simpleProject(pubspecText: ''' name: test '''); var projectDir = createProjectDir(projectContents); var cliRunner = _createCli() .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); // The Dart source code should still be migrated. assertProjectContents(projectDir, simpleProject(migrated: true, pubspecText: // This is strange-looking, but valid. ''' environment: sdk: '>=2.12.0 <3.0.0' name: test ''')); } test_pubspec_is_not_a_map() async { var projectContents = simpleProject(pubspecText: 'not-a-map'); var projectDir = createProjectDir(projectContents); var cliRunner = _createCli() .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); var message = await assertErrorExit( cliRunner, () async => await cliRunner.run(), withUsage: false); expect(message, contains('Failed to parse pubspec file')); } test_pubspec_preserve_collection_dependency() async { var projectContents = simpleProject(sourceText: ''' int firstEven(Iterable x) => x.firstWhere((x) => x.isEven, orElse: () => null); ''', pubspecText: ''' name: test environment: sdk: '>=2.6.0 <3.0.0' dependencies: collection: ^1.16.0 '''); var projectDir = createProjectDir(projectContents); var cliRunner = _createCli() .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); expect(logger.stdoutBuffer.toString(), isNot(contains('Please run `dart pub get`'))); // The Dart source code should still be migrated. assertProjectContents( projectDir, simpleProject(migrated: true, sourceText: ''' import 'package:collection/collection.dart' show IterableExtension; int? firstEven(Iterable x) => x.firstWhereOrNull((x) => x.isEven); ''', pubspecText: ''' name: test environment: sdk: '>=2.12.0 <3.0.0' dependencies: collection: ^1.16.0 ''')); } test_pubspec_update_collection_dependency() async { var projectContents = simpleProject(sourceText: ''' int firstEven(Iterable x) => x.firstWhere((x) => x.isEven, orElse: () => null); ''', pubspecText: ''' name: test environment: sdk: '>=2.6.0 <3.0.0' dependencies: collection: ^1.14.0 '''); var projectDir = createProjectDir(projectContents); var cliRunner = _createCli() .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); expect( logger.stdoutBuffer.toString(), contains('Please run `dart pub get`')); // The Dart source code should still be migrated. assertProjectContents( projectDir, simpleProject(migrated: true, sourceText: ''' import 'package:collection/collection.dart' show IterableExtension; int? firstEven(Iterable x) => x.firstWhereOrNull((x) => x.isEven); ''', pubspecText: ''' name: test environment: sdk: '>=2.12.0 <3.0.0' dependencies: collection: ^1.15.0-nullsafety.4 ''')); } test_pubspec_with_sdk_version_beta() async { var projectDir = createProjectDir(simpleProject()); var cliRunner = _createCli(sdkVersion: '2.12.0-1.2.beta') .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); assertProjectContents( projectDir, simpleProject(migrated: true, pubspecText: ''' name: test environment: sdk: '>=2.12.0-1.2.beta <3.0.0' ''')); } test_pubspec_with_sdk_version_dev() async { var projectDir = createProjectDir(simpleProject()); var cliRunner = _createCli(sdkVersion: '2.12.0-1.2.dev') .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); assertProjectContents( projectDir, simpleProject(migrated: true, pubspecText: ''' name: test environment: sdk: '>=2.12.0-0 <3.0.0' ''')); } test_pubspec_with_sdk_version_edge() async { var projectDir = createProjectDir(simpleProject()); var cliRunner = _createCli(sdkVersion: '2.12.0-edge.1234567') .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); assertProjectContents( projectDir, simpleProject(migrated: true, pubspecText: ''' name: test environment: sdk: '>=2.12.0-0 <3.0.0' ''')); } test_pubspec_with_sdk_version_internal() async { var projectDir = createProjectDir(simpleProject()); var cliRunner = _createCli(sdkVersion: '2.12.0-1234567') .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir])); await cliRunner.run(); assertProjectContents( projectDir, simpleProject(migrated: true, pubspecText: ''' name: test environment: sdk: '>=2.12.0-0 <3.0.0' ''')); } test_uses_physical_resource_provider_by_default() { var cli = MigrationCli(binaryName: 'nnbd_migration'); expect(cli.resourceProvider, same(PhysicalResourceProvider.INSTANCE)); } Future tryGetSourceFromServer(Uri uri, String path) async { var authToken = uri.queryParameters['authToken']; return await httpGet( uri.replace( path: resourceProvider.pathContext.toUri(path).path, queryParameters: {'inline': 'true', 'authToken': authToken}), headers: {'Content-Type': 'application/json; charset=UTF-8'}); } _MigrationCli _createCli( {List nullSafePackages = const [], String sdkVersion}) { mock_sdk.MockSdk( resourceProvider: resourceProvider, nullSafePackages: nullSafePackages, sdkVersion: sdkVersion); return _MigrationCli(this); } void _expectErrorIndicatingCodeIsAlreadyOptedIn() { var errorOutput = logger.stdoutBuffer.toString(); expect(errorOutput, contains('1 analysis issue found:')); expect( errorOutput, contains("A value of type 'Null' can't be returned from the function " "'f' because it has a return type of 'int'")); expect(errorOutput, contains('Set the lower SDK constraint')); } String _getHelpText({@required bool verbose}) { var cliRunner = _createCli().decodeCommandLineArgs(_parseArgs( ['--${CommandLineOptions.helpFlag}', if (verbose) '--verbose'])); expect(cliRunner, isNull); var helpText = logger.stderrBuffer.toString(); return helpText; } String _getPackageConfigText( {@required bool migrated, Map packagesMigrated = const {}}) { Object makePackageEntry(String name, bool migrated, {String rootUri}) { rootUri ??= resourceProvider.pathContext.toUri(packagePath(name)).toString(); return { 'name': name, 'rootUri': rootUri, 'packageUri': 'lib/', 'languageVersion': migrated ? '2.12' : '2.6' }; } var json = { 'configVersion': 2, 'packages': [ makePackageEntry('test', migrated, rootUri: '../'), for (var entry in packagesMigrated.entries) makePackageEntry(entry.key, entry.value) ] }; return JsonEncoder.withIndent(' ').convert(json) + '\n'; } ArgResults _parseArgs(List args) { return MigrationCli.createParser().parse(args); } Future _tellPreviewToApplyChanges(String url) async { var uri = Uri.parse(url); var authToken = uri.queryParameters['authToken']; var response = await httpPost( uri.replace( path: PreviewSite.applyMigrationPath, queryParameters: {'authToken': authToken}), headers: {'Content-Type': 'application/json; charset=UTF-8'}, body: json.encode({'navigationTree': []})); assertHttpSuccess(response); } } @reflectiveTest class _MigrationCliTestPosix extends _MigrationCliTestBase with _MigrationCliTestMethods { @override final resourceProvider; _MigrationCliTestPosix() : resourceProvider = MemoryResourceProvider( context: path.style == path.Style.posix ? null : path.Context( style: path.Style.posix, current: '/working_dir')); } @reflectiveTest class _MigrationCliTestWindows extends _MigrationCliTestBase with _MigrationCliTestMethods { @override final resourceProvider; _MigrationCliTestWindows() : resourceProvider = MemoryResourceProvider( context: path.style == path.Style.windows ? null : path.Context( style: path.Style.windows, current: 'C:\\working_dir')); }