blob: a8410138bda7c6d55a57a7d9da6251dd61c47bfd [file] [log] [blame]
// 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:io';
import 'package:analyzer/file_system/memory_file_system.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer/src/test_utilities/mock_sdk.dart' as mock_sdk;
import 'package:cli_util/cli_logging.dart';
import 'package:http/http.dart' as http;
import 'package:meta/meta.dart';
import 'package:nnbd_migration/migration_cli.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(_MigrationCliTest);
});
}
class _MigrationCli extends MigrationCli {
Completer<void> _previewServerStartedCompleter;
Completer<void> _signalInterruptCompleter;
_MigrationCli(_MigrationCliTest test)
: super(
binaryName: 'nnbd_migration',
loggerFactory: (isVerbose) => test.logger = _TestLogger(isVerbose),
defaultSdkPathOverride: mock_sdk.sdkRoot,
resourceProvider: test.resourceProvider);
@override
Future<void> blockUntilSignalInterrupt() {
_previewServerStartedCompleter.complete();
_signalInterruptCompleter = Completer<void>();
return _signalInterruptCompleter.future;
}
Future<void> runWithPreviewServer(
List<String> args, Future<void> callback()) async {
_previewServerStartedCompleter = Completer<void>();
var done = run(args);
await _previewServerStartedCompleter.future;
await callback();
_signalInterruptCompleter.complete();
return done;
}
}
@reflectiveTest
class _MigrationCliTest {
/*late*/ _TestLogger logger;
final hasVerboseHelpMessage = contains('for verbose help output');
final hasUsageText = contains('Usage: nnbd_migration');
final resourceProvider = MemoryResourceProvider();
String assertErrorExit(MigrationCli cli) {
expect(cli.exitCode, isNotNull);
expect(cli.exitCode, isNot(0));
var stderrText = logger.stderrBuffer.toString();
expect(stderrText, hasUsageText);
expect(stderrText, hasVerboseHelpMessage);
return stderrText;
}
Future<String> assertParseArgsFailure(List<String> args) async {
var cli = _createCli();
await cli.run(args);
var stderrText = assertErrorExit(cli);
expect(stderrText, isNot(contains('Exception')));
return stderrText;
}
CommandLineOptions assertParseArgsSuccess(List<String> args) {
var cli = _createCli();
cli.parseCommandLineArgs(args);
expect(cli.exitCode, isNull);
var options = cli.options;
return options;
}
void assertProjectContents(String projectDir, Map<String, String> 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);
}
}
String createProjectDir(Map<String, String> contents) {
var projectPathPosix = '/test_project';
for (var entry in contents.entries) {
var relativePathPosix = entry.key;
assert(!path.posix.isAbsolute(relativePathPosix));
var filePathPosix = path.posix.join(projectPathPosix, relativePathPosix);
resourceProvider.newFile(
resourceProvider.convertPath(filePathPosix), entry.value);
}
return resourceProvider.convertPath(projectPathPosix);
}
Map<String, String> simpleProject({bool migrated: false}) {
// TODO(paulberry): pubspec needs to be updated when migrating.
return {
'pubspec.yaml': '''
name: test
environment:
sdk: '>=2.6.0 <3.0.0'
''',
'lib/test.dart': '''
int${migrated ? '?' : ''} f() => null;
'''
};
}
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_flag_apply_changes_default() {
expect(assertParseArgsSuccess([]).applyChanges, isFalse);
}
test_flag_apply_changes_disable() async {
// "--no-apply-changes" is not an option.
await assertParseArgsFailure(['--no-apply-changes']);
}
test_flag_apply_changes_enable() {
expect(
assertParseArgsSuccess(['--no-web-preview', '--apply-changes'])
.applyChanges,
isTrue);
}
test_flag_apply_changes_incompatible_with_web_preview() async {
expect(await assertParseArgsFailure(['--web-preview', '--apply-changes']),
contains('--apply-changes requires --no-web-preview'));
}
test_flag_help() async {
var helpText = await _getHelpText(verbose: false);
expect(helpText, hasUsageText);
expect(helpText, hasVerboseHelpMessage);
}
test_flag_help_verbose() async {
var helpText = await _getHelpText(verbose: true);
expect(helpText, hasUsageText);
expect(helpText, isNot(hasVerboseHelpMessage));
}
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_apply_changes() async {
var projectContents = simpleProject();
var projectDir = await createProjectDir(projectContents);
var cli = _createCli();
await cli.run(['--no-web-preview', '--apply-changes', projectDir]);
// Check that a summary was printed
expect(logger.stdoutBuffer.toString(), contains('Applying changes'));
// And that it refers to test.dart
expect(logger.stdoutBuffer.toString(), contains('test.dart'));
// 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
assertProjectContents(projectDir, simpleProject(migrated: true));
}
test_lifecycle_no_preview() async {
var projectContents = simpleProject();
var projectDir = await createProjectDir(projectContents);
var cli = _createCli();
await cli.run(['--no-web-preview', projectDir]);
// Check that a summary was printed
expect(logger.stdoutBuffer.toString(), contains('Summary'));
// And that it refers to test.dart
expect(logger.stdoutBuffer.toString(), contains('test.dart'));
// And that it tells the user they can rerun with `--apply-changes`
expect(logger.stdoutBuffer.toString(), contains('--apply-changes'));
// No changes should have been made
assertProjectContents(projectDir, projectContents);
}
test_lifecycle_preview() async {
var projectContents = simpleProject();
var projectDir = await createProjectDir(projectContents);
var cli = _createCli();
String url;
await cli.runWithPreviewServer([projectDir], () async {
// Server should be running now
url = RegExp('http://.*', multiLine: true)
.stringMatch(logger.stdoutBuffer.toString());
var response = await http.get(url);
expect(response.statusCode, 200);
});
// Server should be stopped now
expect(http.get(url), throwsA(anything));
// And no changes should have been made.
assertProjectContents(projectDir, projectContents);
}
test_migrate_path_none() {
expect(assertParseArgsSuccess([]).directory, Directory.current.path);
}
test_migrate_path_one() {
expect(assertParseArgsSuccess(['foo']).directory, 'foo');
}
test_migrate_path_two() async {
var cli = _createCli();
await cli.run(['foo', 'bar']);
var stderrText = assertErrorExit(cli);
expect(stderrText, contains('No more than one path may be specified'));
}
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() async {
expect(await assertParseArgsFailure(['--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');
cli.parseCommandLineArgs([]);
expect(
File(path.join(cli.options.sdkPath, 'version')).existsSync(), isTrue);
}
test_option_sdk_hidden() async {
var optionName = '--sdk-path';
expect(await _getHelpText(verbose: false), isNot(contains(optionName)));
expect(await _getHelpText(verbose: true), contains(optionName));
}
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_uses_physical_resource_provider_by_default() {
var cli = MigrationCli(binaryName: 'nnbd_migration');
expect(cli.resourceProvider, same(PhysicalResourceProvider.INSTANCE));
}
_MigrationCli _createCli() {
mock_sdk.MockSdk(resourceProvider: resourceProvider);
return _MigrationCli(this);
}
Future<String> _getHelpText({@required bool verbose}) async {
var cli = _createCli();
await cli
.run(['--${CommandLineOptions.helpFlag}', if (verbose) '--verbose']);
expect(cli.exitCode, 0);
var helpText = logger.stderrBuffer.toString();
return helpText;
}
}
/// TODO(paulberry): move into cli_util
class _TestLogger implements Logger {
final stderrBuffer = StringBuffer();
final stdoutBuffer = StringBuffer();
final bool isVerbose;
_TestLogger(this.isVerbose);
@override
Ansi get ansi => Ansi(false);
@override
void flush() {
throw UnimplementedError('TODO(paulberry)');
}
@override
Progress progress(String message) {
return SimpleProgress(this, message);
}
@override
void stderr(String message) {
stderrBuffer.writeln(message);
}
@override
void stdout(String message) {
stdoutBuffer.writeln(message);
}
@override
void trace(String message) {
throw UnimplementedError('TODO(paulberry)');
}
}