Commit a08b5e00 authored by Todd Volkert's avatar Todd Volkert Committed by GitHub

Run command validation on all commands. (#12246)

This makes command validation happen as part of `verifyThenRunCommand()`,
using a newly introduced protected method (`validateCommand()`) rather than
a `commandValidator` property (that subclasses were responsible for manually
invoking).
parent 6420c75f
...@@ -32,22 +32,13 @@ class BuildCommand extends FlutterCommand { ...@@ -32,22 +32,13 @@ class BuildCommand extends FlutterCommand {
@override @override
final String description = 'Flutter build commands.'; final String description = 'Flutter build commands.';
@override
Future<Null> verifyThenRunCommand() async {
commandValidator();
return super.verifyThenRunCommand();
}
@override @override
Future<Null> runCommand() async { } Future<Null> runCommand() async { }
} }
abstract class BuildSubCommand extends FlutterCommand { abstract class BuildSubCommand extends FlutterCommand {
@override BuildSubCommand() {
@mustCallSuper requiresPubspecYaml();
Future<Null> verifyThenRunCommand() async {
commandValidator();
return super.verifyThenRunCommand();
} }
@override @override
...@@ -72,18 +63,16 @@ abstract class BuildSubCommand extends FlutterCommand { ...@@ -72,18 +63,16 @@ abstract class BuildSubCommand extends FlutterCommand {
} }
class BuildCleanCommand extends FlutterCommand { class BuildCleanCommand extends FlutterCommand {
BuildCleanCommand() {
requiresPubspecYaml();
}
@override @override
final String name = 'clean'; final String name = 'clean';
@override @override
final String description = 'Delete the build/ directory.'; final String description = 'Delete the build/ directory.';
@override
Future<Null> verifyThenRunCommand() async {
commandValidator();
return super.verifyThenRunCommand();
}
@override @override
Future<Null> runCommand() async { Future<Null> runCommand() async {
final Directory buildDir = fs.directory(getBuildDirectory()); final Directory buildDir = fs.directory(getBuildDirectory());
......
...@@ -38,6 +38,8 @@ import 'run.dart'; ...@@ -38,6 +38,8 @@ import 'run.dart';
/// exit code. /// exit code.
class DriveCommand extends RunCommandBase { class DriveCommand extends RunCommandBase {
DriveCommand() { DriveCommand() {
requiresPubspecYaml();
argParser.addFlag( argParser.addFlag(
'keep-app-running', 'keep-app-running',
defaultsTo: null, defaultsTo: null,
...@@ -89,12 +91,6 @@ class DriveCommand extends RunCommandBase { ...@@ -89,12 +91,6 @@ class DriveCommand extends RunCommandBase {
// ignore: cancel_subscriptions // ignore: cancel_subscriptions
StreamSubscription<String> _deviceLogSubscription; StreamSubscription<String> _deviceLogSubscription;
@override
Future<Null> verifyThenRunCommand() async {
commandValidator();
return super.verifyThenRunCommand();
}
@override @override
Future<Null> runCommand() async { Future<Null> runCommand() async {
final String testFile = _getTestFile(); final String testFile = _getTestFile();
......
...@@ -12,6 +12,10 @@ import '../globals.dart'; ...@@ -12,6 +12,10 @@ import '../globals.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
class InstallCommand extends FlutterCommand { class InstallCommand extends FlutterCommand {
InstallCommand() {
requiresPubspecYaml();
}
@override @override
final String name = 'install'; final String name = 'install';
...@@ -21,12 +25,11 @@ class InstallCommand extends FlutterCommand { ...@@ -21,12 +25,11 @@ class InstallCommand extends FlutterCommand {
Device device; Device device;
@override @override
Future<Null> verifyThenRunCommand() async { Future<Null> validateCommand() async {
commandValidator(); await super.validateCommand();
device = await findTargetDevice(); device = await findTargetDevice();
if (device == null) if (device == null)
throwToolExit('No target device found'); throwToolExit('No target device found');
return super.verifyThenRunCommand();
} }
@override @override
......
...@@ -26,18 +26,13 @@ class PackagesCommand extends FlutterCommand { ...@@ -26,18 +26,13 @@ class PackagesCommand extends FlutterCommand {
@override @override
final String description = 'Commands for managing Flutter packages.'; final String description = 'Commands for managing Flutter packages.';
@override
Future<Null> verifyThenRunCommand() async {
commandValidator();
return super.verifyThenRunCommand();
}
@override @override
Future<Null> runCommand() async { } Future<Null> runCommand() async { }
} }
class PackagesGetCommand extends FlutterCommand { class PackagesGetCommand extends FlutterCommand {
PackagesGetCommand(this.name, this.upgrade) { PackagesGetCommand(this.name, this.upgrade) {
requiresPubspecYaml();
argParser.addFlag('offline', argParser.addFlag('offline',
negatable: false, negatable: false,
help: 'Use cached packages instead of accessing the network.' help: 'Use cached packages instead of accessing the network.'
...@@ -84,6 +79,10 @@ class PackagesGetCommand extends FlutterCommand { ...@@ -84,6 +79,10 @@ class PackagesGetCommand extends FlutterCommand {
} }
class PackagesTestCommand extends FlutterCommand { class PackagesTestCommand extends FlutterCommand {
PackagesTestCommand() {
requiresPubspecYaml();
}
@override @override
String get name => 'test'; String get name => 'test';
...@@ -107,7 +106,9 @@ class PackagesTestCommand extends FlutterCommand { ...@@ -107,7 +106,9 @@ class PackagesTestCommand extends FlutterCommand {
} }
class PackagesPassthroughCommand extends FlutterCommand { class PackagesPassthroughCommand extends FlutterCommand {
PackagesPassthroughCommand(); PackagesPassthroughCommand() {
requiresPubspecYaml();
}
@override @override
String get name => 'pub'; String get name => 'pub';
......
...@@ -82,6 +82,8 @@ class RunCommand extends RunCommandBase { ...@@ -82,6 +82,8 @@ class RunCommand extends RunCommandBase {
final String description = 'Run your Flutter app on an attached device.'; final String description = 'Run your Flutter app on an attached device.';
RunCommand({ bool verboseHelp: false }) { RunCommand({ bool verboseHelp: false }) {
requiresPubspecYaml();
argParser.addFlag('full-restart', argParser.addFlag('full-restart',
defaultsTo: true, defaultsTo: true,
help: 'Stop any currently running application process before running the app.'); help: 'Stop any currently running application process before running the app.');
...@@ -153,13 +155,6 @@ class RunCommand extends RunCommandBase { ...@@ -153,13 +155,6 @@ class RunCommand extends RunCommandBase {
'measure the startup time and the app restart time, write the\n' 'measure the startup time and the app restart time, write the\n'
'results out to "refresh_benchmark.json", and exit. This flag is\n' 'results out to "refresh_benchmark.json", and exit. This flag is\n'
'intended for use in generating automated flutter benchmarks.'); 'intended for use in generating automated flutter benchmarks.');
commandValidator = () {
// When running with a prebuilt application, no command validation is
// necessary.
if (!runningWithPrebuiltApplication)
commonCommandValidator();
};
} }
List<Device> devices; List<Device> devices;
...@@ -222,14 +217,16 @@ class RunCommand extends RunCommandBase { ...@@ -222,14 +217,16 @@ class RunCommand extends RunCommandBase {
bool get stayResident => argResults['resident']; bool get stayResident => argResults['resident'];
@override @override
Future<FlutterCommandResult> verifyThenRunCommand() async { Future<Null> validateCommand() async {
commandValidator(); // When running with a prebuilt application, no command validation is
// necessary.
if (!runningWithPrebuiltApplication)
await super.validateCommand();
devices = await findAllTargetDevices(); devices = await findAllTargetDevices();
if (devices == null) if (devices == null)
throwToolExit(null); throwToolExit(null);
if (deviceManager.hasSpecifiedAllDevices && runningWithPrebuiltApplication) if (deviceManager.hasSpecifiedAllDevices && runningWithPrebuiltApplication)
throwToolExit('Using -d all with --use-application-binary is not supported'); throwToolExit('Using -d all with --use-application-binary is not supported');
return super.verifyThenRunCommand();
} }
DebuggingOptions _createDebuggingOptions() { DebuggingOptions _createDebuggingOptions() {
......
...@@ -12,6 +12,10 @@ import '../globals.dart'; ...@@ -12,6 +12,10 @@ import '../globals.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
class StopCommand extends FlutterCommand { class StopCommand extends FlutterCommand {
StopCommand() {
requiresPubspecYaml();
}
@override @override
final String name = 'stop'; final String name = 'stop';
...@@ -21,12 +25,11 @@ class StopCommand extends FlutterCommand { ...@@ -21,12 +25,11 @@ class StopCommand extends FlutterCommand {
Device device; Device device;
@override @override
Future<Null> verifyThenRunCommand() async { Future<Null> validateCommand() async {
commandValidator(); await super.validateCommand();
device = await findTargetDevice(); device = await findTargetDevice();
if (device == null) if (device == null)
throwToolExit(null); throwToolExit(null);
return super.verifyThenRunCommand();
} }
@override @override
......
...@@ -21,6 +21,7 @@ import '../test/watcher.dart'; ...@@ -21,6 +21,7 @@ import '../test/watcher.dart';
class TestCommand extends FlutterCommand { class TestCommand extends FlutterCommand {
TestCommand({ bool verboseHelp: false }) { TestCommand({ bool verboseHelp: false }) {
requiresPubspecYaml();
usesPubOption(); usesPubOption();
argParser.addOption('name', argParser.addOption('name',
help: 'A regular expression matching substrings of the names of tests to run.', help: 'A regular expression matching substrings of the names of tests to run.',
...@@ -67,15 +68,6 @@ class TestCommand extends FlutterCommand { ...@@ -67,15 +68,6 @@ class TestCommand extends FlutterCommand {
negatable: false, negatable: false,
help: 'Handle machine structured JSON command input\n' help: 'Handle machine structured JSON command input\n'
'and provide output and progress in machine friendly format.'); 'and provide output and progress in machine friendly format.');
commandValidator = () {
if (!fs.isFileSync('pubspec.yaml')) {
throwToolExit(
'Error: No pubspec.yaml file found in the current working directory.\n'
'Run this command from the root of your project. Test files must be\n'
'called *_test.dart and must reside in the package\'s \'test\'\n'
'directory (or one of its subdirectories).');
}
};
} }
@override @override
...@@ -143,6 +135,18 @@ class TestCommand extends FlutterCommand { ...@@ -143,6 +135,18 @@ class TestCommand extends FlutterCommand {
return true; return true;
} }
@override
Future<Null> validateCommand() async {
await super.validateCommand();
if (!fs.isFileSync('pubspec.yaml')) {
throwToolExit(
'Error: No pubspec.yaml file found in the current working directory.\n'
'Run this command from the root of your project. Test files must be\n'
'called *_test.dart and must reside in the package\'s \'test\'\n'
'directory (or one of its subdirectories).');
}
}
@override @override
Future<FlutterCommandResult> runCommand() async { Future<FlutterCommandResult> runCommand() async {
if (platform.isWindows) { if (platform.isWindows) {
...@@ -152,7 +156,6 @@ class TestCommand extends FlutterCommand { ...@@ -152,7 +156,6 @@ class TestCommand extends FlutterCommand {
); );
} }
commandValidator();
final List<String> names = argResults['name']; final List<String> names = argResults['name'];
final List<String> plainNames = argResults['plain-name']; final List<String> plainNames = argResults['plain-name'];
......
...@@ -21,6 +21,7 @@ const String kFirstUsefulFrameEventName = 'Widgets completed first useful frame' ...@@ -21,6 +21,7 @@ const String kFirstUsefulFrameEventName = 'Widgets completed first useful frame'
class TraceCommand extends FlutterCommand { class TraceCommand extends FlutterCommand {
TraceCommand() { TraceCommand() {
requiresPubspecYaml();
argParser.addFlag('start', negatable: false, help: 'Start tracing.'); argParser.addFlag('start', negatable: false, help: 'Start tracing.');
argParser.addFlag('stop', negatable: false, help: 'Stop tracing.'); argParser.addFlag('stop', negatable: false, help: 'Stop tracing.');
argParser.addOption('out', help: 'Specify the path of the saved trace file.'); argParser.addOption('out', help: 'Specify the path of the saved trace file.');
...@@ -43,12 +44,6 @@ class TraceCommand extends FlutterCommand { ...@@ -43,12 +44,6 @@ class TraceCommand extends FlutterCommand {
'time (controlled by --duration), and stop tracing. To explicitly control tracing, call trace\n' 'time (controlled by --duration), and stop tracing. To explicitly control tracing, call trace\n'
'with --start and later with --stop.'; 'with --start and later with --stop.';
@override
Future<Null> verifyThenRunCommand() async {
commandValidator();
return super.verifyThenRunCommand();
}
@override @override
Future<Null> runCommand() async { Future<Null> runCommand() async {
final int observatoryPort = int.parse(argResults['debug-port']); final int observatoryPort = int.parse(argResults['debug-port']);
......
...@@ -22,8 +22,6 @@ import '../globals.dart'; ...@@ -22,8 +22,6 @@ import '../globals.dart';
import '../usage.dart'; import '../usage.dart';
import 'flutter_command_runner.dart'; import 'flutter_command_runner.dart';
typedef void Validator();
enum ExitStatus { enum ExitStatus {
success, success,
warning, warning,
...@@ -57,13 +55,11 @@ class FlutterCommandResult { ...@@ -57,13 +55,11 @@ class FlutterCommandResult {
} }
abstract class FlutterCommand extends Command<Null> { abstract class FlutterCommand extends Command<Null> {
FlutterCommand() {
commandValidator = commonCommandValidator;
}
@override @override
FlutterCommandRunner get runner => super.runner; FlutterCommandRunner get runner => super.runner;
bool _requiresPubspecYaml = false;
/// Whether this command uses the 'target' option. /// Whether this command uses the 'target' option.
bool _usesTargetOption = false; bool _usesTargetOption = false;
...@@ -75,6 +71,10 @@ abstract class FlutterCommand extends Command<Null> { ...@@ -75,6 +71,10 @@ abstract class FlutterCommand extends Command<Null> {
BuildMode _defaultBuildMode; BuildMode _defaultBuildMode;
void requiresPubspecYaml() {
_requiresPubspecYaml = true;
}
void usesTargetOption() { void usesTargetOption() {
argParser.addOption('target', argParser.addOption('target',
abbr: 't', abbr: 't',
...@@ -219,6 +219,8 @@ abstract class FlutterCommand extends Command<Null> { ...@@ -219,6 +219,8 @@ abstract class FlutterCommand extends Command<Null> {
/// rather than calling [runCommand] directly. /// rather than calling [runCommand] directly.
@mustCallSuper @mustCallSuper
Future<FlutterCommandResult> verifyThenRunCommand() async { Future<FlutterCommandResult> verifyThenRunCommand() async {
await validateCommand();
// Populate the cache. We call this before pub get below so that the sky_engine // Populate the cache. We call this before pub get below so that the sky_engine
// package is available in the flutter cache for pub to find. // package is available in the flutter cache for pub to find.
if (shouldUpdateCache) if (shouldUpdateCache)
...@@ -313,11 +315,10 @@ abstract class FlutterCommand extends Command<Null> { ...@@ -313,11 +315,10 @@ abstract class FlutterCommand extends Command<Null> {
printStatus('No connected devices.'); printStatus('No connected devices.');
} }
// This is a field so that you can modify the value for testing. @protected
Validator commandValidator; @mustCallSuper
Future<Null> validateCommand() async {
void commonCommandValidator() { if (_requiresPubspecYaml && !PackageMap.isUsingCustomPackagesPath) {
if (!PackageMap.isUsingCustomPackagesPath) {
// Don't expect a pubspec.yaml file if the user passed in an explicit .packages file path. // Don't expect a pubspec.yaml file if the user passed in an explicit .packages file path.
if (!fs.isFileSync('pubspec.yaml')) { if (!fs.isFileSync('pubspec.yaml')) {
throw new ToolExit( throw new ToolExit(
...@@ -326,6 +327,7 @@ abstract class FlutterCommand extends Command<Null> { ...@@ -326,6 +327,7 @@ abstract class FlutterCommand extends Command<Null> {
'Do not run this command from the root of your git clone of Flutter.' 'Do not run this command from the root of your git clone of Flutter.'
); );
} }
if (fs.isFileSync('flutter.yaml')) { if (fs.isFileSync('flutter.yaml')) {
throw new ToolExit( throw new ToolExit(
'Please merge your flutter.yaml into your pubspec.yaml.\n\n' 'Please merge your flutter.yaml into your pubspec.yaml.\n\n'
...@@ -343,6 +345,13 @@ abstract class FlutterCommand extends Command<Null> { ...@@ -343,6 +345,13 @@ abstract class FlutterCommand extends Command<Null> {
'https://github.com/flutter/flutter/blob/master/examples/flutter_gallery/pubspec.yaml\n' 'https://github.com/flutter/flutter/blob/master/examples/flutter_gallery/pubspec.yaml\n'
); );
} }
// Validate the current package map only if we will not be running "pub get" later.
if (!(_usesPubOption && argResults['pub'])) {
final String error = new PackageMap(PackageMap.globalPackagesPath).checkValid();
if (error != null)
throw new ToolExit(error);
}
} }
if (_usesTargetOption) { if (_usesTargetOption) {
...@@ -350,13 +359,6 @@ abstract class FlutterCommand extends Command<Null> { ...@@ -350,13 +359,6 @@ abstract class FlutterCommand extends Command<Null> {
if (!fs.isFileSync(targetPath)) if (!fs.isFileSync(targetPath))
throw new ToolExit('Target file "$targetPath" not found.'); throw new ToolExit('Target file "$targetPath" not found.');
} }
// Validate the current package map only if we will not be running "pub get" later.
if (!(_usesPubOption && argResults['pub'])) {
final String error = new PackageMap(PackageMap.globalPackagesPath).checkValid();
if (error != null)
throw new ToolExit(error);
}
} }
ApplicationPackageStore applicationPackages; ApplicationPackageStore applicationPackages;
......
...@@ -76,10 +76,11 @@ void main() { ...@@ -76,10 +76,11 @@ void main() {
final String testApp = fs.path.join(cwd.path, 'test', 'e2e.dart'); final String testApp = fs.path.join(cwd.path, 'test', 'e2e.dart');
final String testFile = fs.path.join(cwd.path, 'test_driver', 'e2e_test.dart'); final String testFile = fs.path.join(cwd.path, 'test_driver', 'e2e_test.dart');
fs.file(testApp).createSync(recursive: true);
final List<String> args = <String>[ final List<String> args = <String>[
'drive', 'drive',
'--target=$testApp}', '--target=$testApp',
]; ];
try { try {
await createTestCommandRunner(command).run(args); await createTestCommandRunner(command).run(args);
...@@ -120,6 +121,7 @@ void main() { ...@@ -120,6 +121,7 @@ void main() {
testUsingContext('returns 1 when app file is outside package', () async { testUsingContext('returns 1 when app file is outside package', () async {
final String appFile = fs.path.join(cwd.dirname, 'other_app', 'app.dart'); final String appFile = fs.path.join(cwd.dirname, 'other_app', 'app.dart');
fs.file(appFile).createSync(recursive: true);
final List<String> args = <String>[ final List<String> args = <String>[
'drive', 'drive',
'--target=$appFile', '--target=$appFile',
...@@ -139,6 +141,7 @@ void main() { ...@@ -139,6 +141,7 @@ void main() {
testUsingContext('returns 1 when app file is in the root dir', () async { testUsingContext('returns 1 when app file is in the root dir', () async {
final String appFile = fs.path.join(cwd.path, 'main.dart'); final String appFile = fs.path.join(cwd.path, 'main.dart');
fs.file(appFile).createSync(recursive: true);
final List<String> args = <String>[ final List<String> args = <String>[
'drive', 'drive',
'--target=$appFile', '--target=$appFile',
......
...@@ -103,8 +103,7 @@ class MockDeviceLogReader extends DeviceLogReader { ...@@ -103,8 +103,7 @@ class MockDeviceLogReader extends DeviceLogReader {
void applyMocksToCommand(FlutterCommand command) { void applyMocksToCommand(FlutterCommand command) {
command command
..applicationPackages = new MockApplicationPackageStore() ..applicationPackages = new MockApplicationPackageStore();
..commandValidator = () => true;
} }
/// Common functionality for tracking mock interaction /// Common functionality for tracking mock interaction
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment