| Index: tools/testing/dart/command.dart
|
| diff --git a/tools/testing/dart/command.dart b/tools/testing/dart/command.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..e191186234fee1b96aad1d537be351553a014366
|
| --- /dev/null
|
| +++ b/tools/testing/dart/command.dart
|
| @@ -0,0 +1,609 @@
|
| +// Copyright (c) 2017, 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';
|
| +// We need to use the 'io' prefix here, otherwise io.exitCode will shadow
|
| +// CommandOutput.exitCode in subclasses of CommandOutput.
|
| +import 'dart:io' as io;
|
| +
|
| +import 'command_output.dart';
|
| +import 'configuration.dart';
|
| +import 'expectation.dart';
|
| +import 'path.dart';
|
| +import 'utils.dart';
|
| +
|
| +/// A command executed as a step in a test case.
|
| +class Command {
|
| + static Command contentShell(
|
| + String executable,
|
| + String htmlFile,
|
| + List<String> options,
|
| + List<String> dartFlags,
|
| + Map<String, String> environment) {
|
| + return new ContentShellCommand._(
|
| + executable, htmlFile, options, dartFlags, environment);
|
| + }
|
| +
|
| + static Command browserTest(String url, Configuration configuration,
|
| + {bool retry}) {
|
| + return new BrowserTestCommand._(url, configuration, retry);
|
| + }
|
| +
|
| + static Command browserHtmlTest(
|
| + String url, Configuration configuration, List<String> expectedMessages,
|
| + {bool retry}) {
|
| + return new BrowserHtmlTestCommand._(
|
| + url, configuration, expectedMessages, retry);
|
| + }
|
| +
|
| + static Command compilation(
|
| + String displayName,
|
| + String outputFile,
|
| + bool neverSkipCompilation,
|
| + List<Uri> bootstrapDependencies,
|
| + String executable,
|
| + List<String> arguments,
|
| + Map<String, String> environment) {
|
| + return new CompilationCommand._(
|
| + displayName,
|
| + outputFile,
|
| + neverSkipCompilation,
|
| + bootstrapDependencies,
|
| + executable,
|
| + arguments,
|
| + environment);
|
| + }
|
| +
|
| + static Command kernelCompilation(
|
| + String outputFile,
|
| + bool neverSkipCompilation,
|
| + List<Uri> bootstrapDependencies,
|
| + String executable,
|
| + List<String> arguments,
|
| + Map<String, String> environment) {
|
| + return new KernelCompilationCommand._(outputFile, neverSkipCompilation,
|
| + bootstrapDependencies, executable, arguments, environment);
|
| + }
|
| +
|
| + static Command analysis(String executable, List<String> arguments,
|
| + Map<String, String> environmentOverrides) {
|
| + return new AnalysisCommand._(executable, arguments, environmentOverrides);
|
| + }
|
| +
|
| + static Command vm(String executable, List<String> arguments,
|
| + Map<String, String> environmentOverrides) {
|
| + return new VmCommand._(executable, arguments, environmentOverrides);
|
| + }
|
| +
|
| + static Command vmBatch(String executable, String tester,
|
| + List<String> arguments, Map<String, String> environmentOverrides,
|
| + {bool checked: true}) {
|
| + return new VmBatchCommand._(
|
| + executable, tester, arguments, environmentOverrides,
|
| + checked: checked);
|
| + }
|
| +
|
| + static Command adbPrecompiled(String precompiledRunner, String processTest,
|
| + String testDirectory, List<String> arguments, bool useBlobs) {
|
| + return new AdbPrecompilationCommand._(
|
| + precompiledRunner, processTest, testDirectory, arguments, useBlobs);
|
| + }
|
| +
|
| + static Command jsCommandLine(
|
| + String displayName, String executable, List<String> arguments,
|
| + [Map<String, String> environment]) {
|
| + return new JSCommandlineCommand._(
|
| + displayName, executable, arguments, environment);
|
| + }
|
| +
|
| + static Command process(
|
| + String displayName, String executable, List<String> arguments,
|
| + [Map<String, String> environment, String workingDirectory]) {
|
| + return new ProcessCommand._(
|
| + displayName, executable, arguments, environment, workingDirectory);
|
| + }
|
| +
|
| + static Command copy(String sourceDirectory, String destinationDirectory) {
|
| + return new CleanDirectoryCopyCommand._(
|
| + sourceDirectory, destinationDirectory);
|
| + }
|
| +
|
| + static Command pub(String pubCommand, String pubExecutable,
|
| + String pubspecYamlDirectory, String pubCacheDirectory,
|
| + {List<String> arguments: const <String>[]}) {
|
| + return new PubCommand._(pubCommand, pubExecutable, pubspecYamlDirectory,
|
| + pubCacheDirectory, arguments);
|
| + }
|
| +
|
| + static Command makeSymlink(String link, String target) {
|
| + return new MakeSymlinkCommand._(link, target);
|
| + }
|
| +
|
| + /// A descriptive name for this command.
|
| + final String displayName;
|
| +
|
| + /// Number of times this command *can* be retried.
|
| + int get maxNumRetries => 2;
|
| +
|
| + /// Reproduction command.
|
| + String get reproductionCommand => null;
|
| +
|
| + /// We compute the Command.hashCode lazily and cache it here, since it might
|
| + /// be expensive to compute (and hashCode is called often).
|
| + int _cachedHashCode;
|
| +
|
| + Command._(this.displayName);
|
| +
|
| + int get hashCode {
|
| + if (_cachedHashCode == null) {
|
| + var builder = new HashCodeBuilder();
|
| + _buildHashCode(builder);
|
| + _cachedHashCode = builder.value;
|
| + }
|
| + return _cachedHashCode;
|
| + }
|
| +
|
| + operator ==(Object other) =>
|
| + identical(this, other) ||
|
| + (runtimeType == other.runtimeType && _equal(other as Command));
|
| +
|
| + void _buildHashCode(HashCodeBuilder builder) {
|
| + builder.addJson(displayName);
|
| + }
|
| +
|
| + bool _equal(covariant Command other) =>
|
| + hashCode == other.hashCode && displayName == other.displayName;
|
| +
|
| + String toString() => reproductionCommand;
|
| +
|
| + Future<bool> get outputIsUpToDate => new Future.value(false);
|
| +}
|
| +
|
| +class ProcessCommand extends Command {
|
| + /// Path to the executable of this command.
|
| + String executable;
|
| +
|
| + /// Command line arguments to the executable.
|
| + final List<String> arguments;
|
| +
|
| + /// Environment for the command.
|
| + final Map<String, String> environmentOverrides;
|
| +
|
| + /// Working directory for the command.
|
| + final String workingDirectory;
|
| +
|
| + ProcessCommand._(String displayName, this.executable, this.arguments,
|
| + [this.environmentOverrides, this.workingDirectory])
|
| + : super._(displayName) {
|
| + if (io.Platform.operatingSystem == 'windows') {
|
| + // Windows can't handle the first command if it is a .bat file or the like
|
| + // with the slashes going the other direction.
|
| + // NOTE: Issue 1306
|
| + executable = executable.replaceAll('/', '\\');
|
| + }
|
| + }
|
| +
|
| + void _buildHashCode(HashCodeBuilder builder) {
|
| + super._buildHashCode(builder);
|
| + builder.addJson(executable);
|
| + builder.addJson(workingDirectory);
|
| + builder.addJson(arguments);
|
| + builder.addJson(environmentOverrides);
|
| + }
|
| +
|
| + bool _equal(ProcessCommand other) =>
|
| + super._equal(other) &&
|
| + executable == other.executable &&
|
| + deepJsonCompare(arguments, other.arguments) &&
|
| + workingDirectory == other.workingDirectory &&
|
| + deepJsonCompare(environmentOverrides, other.environmentOverrides);
|
| +
|
| + String get reproductionCommand {
|
| + var env = new StringBuffer();
|
| + environmentOverrides?.forEach((key, value) =>
|
| + (io.Platform.operatingSystem == 'windows')
|
| + ? env.write('set $key=${escapeCommandLineArgument(value)} & ')
|
| + : env.write('$key=${escapeCommandLineArgument(value)} '));
|
| + var command = ([executable]..addAll(batchArguments)..addAll(arguments))
|
| + .map(escapeCommandLineArgument)
|
| + .join(' ');
|
| + if (workingDirectory != null) {
|
| + command = "$command (working directory: $workingDirectory)";
|
| + }
|
| + return "$env$command";
|
| + }
|
| +
|
| + Future<bool> get outputIsUpToDate => new Future.value(false);
|
| +
|
| + /// Arguments that are passed to the process when starting batch mode.
|
| + ///
|
| + /// In non-batch mode, they should be passed before [arguments].
|
| + List<String> get batchArguments => const [];
|
| +}
|
| +
|
| +class CompilationCommand extends ProcessCommand {
|
| + final String _outputFile;
|
| + final bool _neverSkipCompilation;
|
| + final List<Uri> _bootstrapDependencies;
|
| +
|
| + CompilationCommand._(
|
| + String displayName,
|
| + this._outputFile,
|
| + this._neverSkipCompilation,
|
| + this._bootstrapDependencies,
|
| + String executable,
|
| + List<String> arguments,
|
| + Map<String, String> environmentOverrides)
|
| + : super._(displayName, executable, arguments, environmentOverrides);
|
| +
|
| + Future<bool> get outputIsUpToDate {
|
| + if (_neverSkipCompilation) return new Future.value(false);
|
| +
|
| + Future<List<Uri>> readDepsFile(String path) {
|
| + var file = new io.File(new Path(path).toNativePath());
|
| + if (!file.existsSync()) {
|
| + return new Future.value(null);
|
| + }
|
| + return file.readAsLines().then((List<String> lines) {
|
| + var dependencies = new List<Uri>();
|
| + for (var line in lines) {
|
| + line = line.trim();
|
| + if (line.length > 0) {
|
| + dependencies.add(Uri.parse(line));
|
| + }
|
| + }
|
| + return dependencies;
|
| + });
|
| + }
|
| +
|
| + return readDepsFile("$_outputFile.deps").then((dependencies) {
|
| + if (dependencies != null) {
|
| + dependencies.addAll(_bootstrapDependencies);
|
| + var jsOutputLastModified = TestUtils.lastModifiedCache
|
| + .getLastModified(new Uri(scheme: 'file', path: _outputFile));
|
| + if (jsOutputLastModified != null) {
|
| + for (var dependency in dependencies) {
|
| + var dependencyLastModified =
|
| + TestUtils.lastModifiedCache.getLastModified(dependency);
|
| + if (dependencyLastModified == null ||
|
| + dependencyLastModified.isAfter(jsOutputLastModified)) {
|
| + return false;
|
| + }
|
| + }
|
| + return true;
|
| + }
|
| + }
|
| + return false;
|
| + });
|
| + }
|
| +
|
| + void _buildHashCode(HashCodeBuilder builder) {
|
| + super._buildHashCode(builder);
|
| + builder.addJson(_outputFile);
|
| + builder.addJson(_neverSkipCompilation);
|
| + builder.addJson(_bootstrapDependencies);
|
| + }
|
| +
|
| + bool _equal(CompilationCommand other) =>
|
| + super._equal(other) &&
|
| + _outputFile == other._outputFile &&
|
| + _neverSkipCompilation == other._neverSkipCompilation &&
|
| + deepJsonCompare(_bootstrapDependencies, other._bootstrapDependencies);
|
| +}
|
| +
|
| +class KernelCompilationCommand extends CompilationCommand {
|
| + KernelCompilationCommand._(
|
| + String outputFile,
|
| + bool neverSkipCompilation,
|
| + List<Uri> bootstrapDependencies,
|
| + String executable,
|
| + List<String> arguments,
|
| + Map<String, String> environmentOverrides)
|
| + : super._('dartk', outputFile, neverSkipCompilation,
|
| + bootstrapDependencies, executable, arguments, environmentOverrides);
|
| +
|
| + int get maxNumRetries => 1;
|
| +}
|
| +
|
| +/// This is just a Pair(String, Map) class with hashCode and operator ==
|
| +class AddFlagsKey {
|
| + final String flags;
|
| + final Map env;
|
| + AddFlagsKey(this.flags, this.env);
|
| + // Just use object identity for environment map
|
| + bool operator ==(Object other) =>
|
| + other is AddFlagsKey && flags == other.flags && env == other.env;
|
| + int get hashCode => flags.hashCode ^ env.hashCode;
|
| +}
|
| +
|
| +class ContentShellCommand extends ProcessCommand {
|
| + ContentShellCommand._(
|
| + String executable,
|
| + String htmlFile,
|
| + List<String> options,
|
| + List<String> dartFlags,
|
| + Map<String, String> environmentOverrides)
|
| + : super._("content_shell", executable, _getArguments(options, htmlFile),
|
| + _getEnvironment(environmentOverrides, dartFlags));
|
| +
|
| + // Cache the modified environments in a map from the old environment and
|
| + // the string of Dart flags to the new environment. Avoid creating new
|
| + // environment object for each command object.
|
| + static Map<AddFlagsKey, Map<String, String>> environments = {};
|
| +
|
| + static Map<String, String> _getEnvironment(
|
| + Map<String, String> env, List<String> dartFlags) {
|
| + var needDartFlags = dartFlags != null && dartFlags.isNotEmpty;
|
| + if (needDartFlags) {
|
| + if (env == null) {
|
| + env = const <String, String>{};
|
| + }
|
| + var flags = dartFlags.join(' ');
|
| + return environments.putIfAbsent(
|
| + new AddFlagsKey(flags, env),
|
| + () => new Map<String, String>.from(env)
|
| + ..addAll({'DART_FLAGS': flags, 'DART_FORWARDING_PRINT': '1'}));
|
| + }
|
| + return env;
|
| + }
|
| +
|
| + static List<String> _getArguments(List<String> options, String htmlFile) {
|
| + var arguments = options.toList();
|
| + arguments.add(htmlFile);
|
| + return arguments;
|
| + }
|
| +
|
| + int get maxNumRetries => 3;
|
| +}
|
| +
|
| +class BrowserTestCommand extends Command {
|
| + Runtime get browser => configuration.runtime;
|
| + final String url;
|
| + final Configuration configuration;
|
| + final bool retry;
|
| +
|
| + BrowserTestCommand._(this.url, this.configuration, this.retry)
|
| + : super._(configuration.runtime.name);
|
| +
|
| + void _buildHashCode(HashCodeBuilder builder) {
|
| + super._buildHashCode(builder);
|
| + builder.addJson(browser.name);
|
| + builder.addJson(url);
|
| + builder.add(configuration);
|
| + builder.add(retry);
|
| + }
|
| +
|
| + bool _equal(BrowserTestCommand other) =>
|
| + super._equal(other) &&
|
| + browser == other.browser &&
|
| + url == other.url &&
|
| + identical(configuration, other.configuration) &&
|
| + retry == other.retry;
|
| +
|
| + String get reproductionCommand {
|
| + var parts = [
|
| + io.Platform.resolvedExecutable,
|
| + 'tools/testing/dart/launch_browser.dart',
|
| + browser.name,
|
| + url
|
| + ];
|
| + return parts.map(escapeCommandLineArgument).join(' ');
|
| + }
|
| +
|
| + int get maxNumRetries => 4;
|
| +}
|
| +
|
| +class BrowserHtmlTestCommand extends BrowserTestCommand {
|
| + List<String> expectedMessages;
|
| + BrowserHtmlTestCommand._(String url, Configuration configuration,
|
| + this.expectedMessages, bool retry)
|
| + : super._(url, configuration, retry);
|
| +
|
| + void _buildHashCode(HashCodeBuilder builder) {
|
| + super._buildHashCode(builder);
|
| + builder.addJson(expectedMessages);
|
| + }
|
| +
|
| + bool _equal(BrowserHtmlTestCommand other) =>
|
| + super._equal(other) &&
|
| + identical(expectedMessages, other.expectedMessages);
|
| +}
|
| +
|
| +class AnalysisCommand extends ProcessCommand {
|
| + AnalysisCommand._(String executable, List<String> arguments,
|
| + Map<String, String> environmentOverrides)
|
| + : super._('dart2analyzer', executable, arguments, environmentOverrides);
|
| +}
|
| +
|
| +class VmCommand extends ProcessCommand {
|
| + VmCommand._(String executable, List<String> arguments,
|
| + Map<String, String> environmentOverrides)
|
| + : super._('vm', executable, arguments, environmentOverrides);
|
| +}
|
| +
|
| +class VmBatchCommand extends ProcessCommand implements VmCommand {
|
| + final String dartFile;
|
| + final bool checked;
|
| +
|
| + VmBatchCommand._(String executable, String dartFile, List<String> arguments,
|
| + Map<String, String> environmentOverrides,
|
| + {this.checked: true})
|
| + : this.dartFile = dartFile,
|
| + super._('vm-batch', executable, arguments, environmentOverrides);
|
| +
|
| + @override
|
| + List<String> get batchArguments =>
|
| + checked ? ['--checked', dartFile] : [dartFile];
|
| +
|
| + @override
|
| + bool _equal(VmBatchCommand other) {
|
| + return super._equal(other) &&
|
| + dartFile == other.dartFile &&
|
| + checked == other.checked;
|
| + }
|
| +
|
| + @override
|
| + void _buildHashCode(HashCodeBuilder builder) {
|
| + super._buildHashCode(builder);
|
| + builder.addJson(dartFile);
|
| + builder.addJson(checked);
|
| + }
|
| +}
|
| +
|
| +class AdbPrecompilationCommand extends Command {
|
| + final String precompiledRunnerFilename;
|
| + final String processTestFilename;
|
| + final String precompiledTestDirectory;
|
| + final List<String> arguments;
|
| + final bool useBlobs;
|
| +
|
| + AdbPrecompilationCommand._(
|
| + this.precompiledRunnerFilename,
|
| + this.processTestFilename,
|
| + this.precompiledTestDirectory,
|
| + this.arguments,
|
| + this.useBlobs)
|
| + : super._("adb_precompilation");
|
| +
|
| + void _buildHashCode(HashCodeBuilder builder) {
|
| + super._buildHashCode(builder);
|
| + builder.add(precompiledRunnerFilename);
|
| + builder.add(precompiledTestDirectory);
|
| + builder.add(arguments);
|
| + builder.add(useBlobs);
|
| + }
|
| +
|
| + bool _equal(AdbPrecompilationCommand other) =>
|
| + super._equal(other) &&
|
| + precompiledRunnerFilename == other.precompiledRunnerFilename &&
|
| + useBlobs == other.useBlobs &&
|
| + arguments == other.arguments &&
|
| + precompiledTestDirectory == other.precompiledTestDirectory;
|
| +
|
| + String toString() => 'Steps to push precompiled runner and precompiled code '
|
| + 'to an attached device. Uses (and requires) adb.';
|
| +}
|
| +
|
| +class JSCommandlineCommand extends ProcessCommand {
|
| + JSCommandlineCommand._(
|
| + String displayName, String executable, List<String> arguments,
|
| + [Map<String, String> environmentOverrides = null])
|
| + : super._(displayName, executable, arguments, environmentOverrides);
|
| +}
|
| +
|
| +class PubCommand extends ProcessCommand {
|
| + final String command;
|
| +
|
| + PubCommand._(String pubCommand, String pubExecutable,
|
| + String pubspecYamlDirectory, String pubCacheDirectory, List<String> args)
|
| + : command = pubCommand,
|
| + super._(
|
| + 'pub_$pubCommand',
|
| + new io.File(pubExecutable).absolute.path,
|
| + [pubCommand]..addAll(args),
|
| + {'PUB_CACHE': pubCacheDirectory},
|
| + pubspecYamlDirectory);
|
| +
|
| + void _buildHashCode(HashCodeBuilder builder) {
|
| + super._buildHashCode(builder);
|
| + builder.addJson(command);
|
| + }
|
| +
|
| + bool _equal(PubCommand other) =>
|
| + super._equal(other) && command == other.command;
|
| +}
|
| +
|
| +/// [ScriptCommand]s are executed by dart code.
|
| +abstract class ScriptCommand extends Command {
|
| + ScriptCommand._(String displayName) : super._(displayName);
|
| +
|
| + Future<ScriptCommandOutputImpl> run();
|
| +}
|
| +
|
| +class CleanDirectoryCopyCommand extends ScriptCommand {
|
| + final String _sourceDirectory;
|
| + final String _destinationDirectory;
|
| +
|
| + CleanDirectoryCopyCommand._(this._sourceDirectory, this._destinationDirectory)
|
| + : super._('dir_copy');
|
| +
|
| + String get reproductionCommand =>
|
| + "Copying '$_sourceDirectory' to '$_destinationDirectory'.";
|
| +
|
| + Future<ScriptCommandOutputImpl> run() {
|
| + var watch = new Stopwatch()..start();
|
| +
|
| + var destination = new io.Directory(_destinationDirectory);
|
| +
|
| + return destination.exists().then((bool exists) {
|
| + Future cleanDirectoryFuture;
|
| + if (exists) {
|
| + cleanDirectoryFuture = TestUtils.deleteDirectory(_destinationDirectory);
|
| + } else {
|
| + cleanDirectoryFuture = new Future.value(null);
|
| + }
|
| + return cleanDirectoryFuture.then((_) {
|
| + return TestUtils.copyDirectory(_sourceDirectory, _destinationDirectory);
|
| + });
|
| + }).then((_) {
|
| + return new ScriptCommandOutputImpl(
|
| + this, Expectation.pass, "", watch.elapsed);
|
| + }).catchError((error) {
|
| + return new ScriptCommandOutputImpl(
|
| + this, Expectation.fail, "An error occured: $error.", watch.elapsed);
|
| + });
|
| + }
|
| +
|
| + void _buildHashCode(HashCodeBuilder builder) {
|
| + super._buildHashCode(builder);
|
| + builder.addJson(_sourceDirectory);
|
| + builder.addJson(_destinationDirectory);
|
| + }
|
| +
|
| + bool _equal(CleanDirectoryCopyCommand other) =>
|
| + super._equal(other) &&
|
| + _sourceDirectory == other._sourceDirectory &&
|
| + _destinationDirectory == other._destinationDirectory;
|
| +}
|
| +
|
| +/// Makes a symbolic link to another directory.
|
| +class MakeSymlinkCommand extends ScriptCommand {
|
| + String _link;
|
| + String _target;
|
| +
|
| + MakeSymlinkCommand._(this._link, this._target) : super._('make_symlink');
|
| +
|
| + String get reproductionCommand =>
|
| + "Make symbolic link '$_link' (target: $_target)'.";
|
| +
|
| + Future<ScriptCommandOutputImpl> run() {
|
| + var watch = new Stopwatch()..start();
|
| + var targetFile = new io.Directory(_target);
|
| + return targetFile.exists().then((bool targetExists) {
|
| + if (!targetExists) {
|
| + throw new Exception("Target '$_target' does not exist");
|
| + }
|
| + var link = new io.Link(_link);
|
| +
|
| + return link.exists().then((bool exists) {
|
| + if (exists) return link.delete();
|
| + }).then((_) => link.create(_target));
|
| + }).then((_) {
|
| + return new ScriptCommandOutputImpl(
|
| + this, Expectation.pass, "", watch.elapsed);
|
| + }).catchError((error) {
|
| + return new ScriptCommandOutputImpl(
|
| + this, Expectation.fail, "An error occured: $error.", watch.elapsed);
|
| + });
|
| + }
|
| +
|
| + void _buildHashCode(HashCodeBuilder builder) {
|
| + super._buildHashCode(builder);
|
| + builder.addJson(_link);
|
| + builder.addJson(_target);
|
| + }
|
| +
|
| + bool _equal(MakeSymlinkCommand other) =>
|
| + super._equal(other) && _link == other._link && _target == other._target;
|
| +}
|
|
|