blob: ca411af6c2058251c7eef6194090c849a803f12a [file] [edit]
// Copyright (c) 2025, 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:convert';
import 'dart:io';
import 'package:pub_formats/pub_formats.dart';
import 'package:test/test.dart';
import '../utils.dart';
import 'helpers.dart';
final _sdkUri = resolveDartDevUri('.').resolve('../../');
final _package2RelativePath = Uri.directory('pkg/dartdev/test/data/dart_app/');
final _package2Dir = Directory.fromUri(
_sdkUri.resolveUri(_package2RelativePath),
);
final _pathEnvVarSeparator = Platform.isWindows ? ';' : ':';
const String _dartDirectoryEnvKey = 'DART_DATA_HOME';
// Set to true for debugging dartdev from the test.
const fromDartdevSource = false;
void main() async {
if (!nativeAssetsExperimentAvailableOnCurrentChannel) {
return;
}
final errorExitCode = fromDartdevSource
? /* Dartdev doesn't exit the process, it sends a message to the VM.*/ 0
: 255;
final usageExitCode = fromDartdevSource
? /* Dartdev doesn't exit the process, it sends a message to the VM.*/ 0
: 64;
test('dart run --help', timeout: longTimeout, () async {
final result = await _runDartdev(
fromDartdevSource,
'run',
['--help'],
null,
{},
);
printOnFailure('stdout:\n${result.stdout}');
expect(
result.stdout,
contains('''
Run a Dart program from a file or a local or remote package.
Usage:
Running a local script or package executable:
dart run [vm-options] <dart-file>|<local-package>[:<executable>] args
Running a remote package executable:
dart run <remote-package>[:<executable?]@[<descriptor>]> [args]
<dart-file>
A path to a Dart script (e.g., `bin/main.dart`).
<local-package>
The name of a package in the local package resolution.
<executable>
The name of an executable in the package to execute.
For example, `dart run test:test` runs the `test` executable from the `test` package.
If the executable is not specified, the package name is used.
<descriptor>
A YAML formatted string that describes how to locate the
remote package, the same you could use in a pubspec.
For example, to run the latest stable `pubviz` package from pub.dev:
dart run pubviz@
To specify a version constraint:
dart run pubviz@^4.0.0
To specify a custom package host:
dart run 'pubviz@{hosted: https://my_repository.com, version: ^1.0.0}'
To run from a git package:
dart run 'pubviz@{git: https://github.com/kevmoo/pubviz}'
See https://dart.dev/to/package-descriptors for more details.'''),
);
});
for (final argument in [
'vm_snapshot_analysis:snapshot_analysis@0.7.5',
'vm_snapshot_analysis:snapshot_analysis@^0.7.5',
'vm_snapshot_analysis:snapshot_analysis@', // Resolves to latest stable version.
'vm_snapshot_analysis:snapshot_analysis@{hosted: https://pub.dev}',
]) {
test('dart run $argument', timeout: longTimeout, () async {
await inTempDir((tempUri) async {
final dartDataHome = tempUri.resolve('dart_home/');
await Directory.fromUri(dartDataHome).create();
final binDir = Directory.fromUri(dartDataHome.resolve('install/bin'));
final environment = <String, String>{
_dartDirectoryEnvKey: dartDataHome.toFilePath(),
'PATH':
'${binDir.path}$_pathEnvVarSeparator${Platform.environment['PATH']!}',
};
final runResult = await _runDartdev(
fromDartdevSource,
'run',
[
argument,
// Make sure to pass arguments that influence stdout.
'compare',
'--help',
],
null,
environment,
);
expect(
runResult.stdout,
stringContainsInOrder([
'Usage: snapshot_analysis compare <old.json> <new.json>',
]),
);
expect(runResult.exitCode, 0);
});
});
}
test('dart run remote from git', timeout: longTimeout, () async {
await inTempDir((tempUri) async {
final (gitUri, gitRef) = await _setupSimpleGitRepo(tempUri);
for (final (argument, expectedResult) in [
('dart_app@{git: ${gitUri.toString()}}', 'Hello Alice and Bob'),
(
'dart_app:other_app@{git: ${gitUri.toString()}}',
'Hello from other app Alice and Bob',
),
]) {
final arguments = [
argument,
// Make sure to pass arguments that influence stdout.
'Alice',
'and',
'Bob',
];
final dartDataHome = tempUri.resolve('dart_home/');
await Directory.fromUri(dartDataHome).create();
final binDir = Directory.fromUri(dartDataHome.resolve('install/bin'));
final environment = {
_dartDirectoryEnvKey: dartDataHome.toFilePath(),
'PATH':
'${binDir.path}$_pathEnvVarSeparator${Platform.environment['PATH']!}',
};
final runResult = await _runDartdev(
fromDartdevSource,
'run',
arguments,
null,
environment,
);
expect(runResult.stdout, contains(expectedResult));
expect(runResult.exitCode, 0);
}
});
});
final errorArgumentss = [
(
['this_package_does_not_exist_12345@'],
'could not find package this_package_does_not_exist_12345 at',
errorExitCode,
),
(
['--enable-asserts', 'vm_snapshot_analysis@'],
'--enable-asserts cannot be used in remote runs',
usageExitCode,
),
(['my_package@{,bad,descriptor,}'], '{,bad,descriptor,}', usageExitCode),
];
for (final (errorArguments, error, exitCode) in errorArgumentss) {
test('dart run ${errorArguments.join(' ')}', timeout: longTimeout, () async {
await inTempDir((tempUri) async {
final dartDataHome = tempUri.resolve('dart_home/');
await Directory.fromUri(dartDataHome).create();
final binDir = Directory.fromUri(dartDataHome.resolve('install/bin'));
final environment = <String, String>{
_dartDirectoryEnvKey: dartDataHome.toFilePath(),
'PATH':
'${binDir.path}$_pathEnvVarSeparator${Platform.environment['PATH']!}',
};
final runResult = await _runDartdev(
fromDartdevSource,
'run',
errorArguments,
null,
environment,
expectedExitCode: exitCode,
);
expect(runResult.stderr, contains(error));
});
});
}
test(
'dart run error from git with build hook failure',
timeout: longTimeout,
() async {
await inTempDir((tempUri) async {
final dartDataHome = tempUri.resolve('dart_home/');
await Directory.fromUri(dartDataHome).create();
final binDir = Directory.fromUri(dartDataHome.resolve('install/bin'));
final environment = {
_dartDirectoryEnvKey: dartDataHome.toFilePath(),
'PATH':
'${binDir.path}$_pathEnvVarSeparator${Platform.environment['PATH']!}',
};
const packageName = 'test_app_with_failing_hook';
final (gitUri, _) = await _setupGitRepo(
tempUri,
repoName: '$packageName.git',
files: {
'pubspec.yaml': jsonEncode(
PubspecYamlFileSyntax(
name: packageName,
environment: EnvironmentSyntax(
sdk: '^${Platform.version.split(' ').first}',
),
executables: {packageName: packageName},
).json,
),
'bin/$packageName.dart': '''
void main(List<String> args) {
print('This should not be printed.');
}
''',
'hook/build.dart': '''
void main(List<String> args) async {
throw Exception('This build hook is designed to fail.');
}
''',
},
);
final runResult = await _runDartdev(
fromDartdevSource,
'run',
['test_app_with_failing_hook@{git: ${gitUri.toFilePath()}}'],
null,
environment,
expectedExitCode: errorExitCode,
);
expect(
runResult.stderr,
contains('This build hook is designed to fail.'),
);
expect(
runResult.stdout,
isNot(contains('This should not be printed.')),
);
});
},
);
test('dart run caches git package', timeout: longTimeout, () async {
await inTempDir((tempUri) async {
final (gitUri, gitRef) = await _setupSimpleGitRepo(tempUri);
final arguments = ['dart_app@{git: ${gitUri.toFilePath()}}', 'World'];
// 2. Setup environment
final dartDataHome = tempUri.resolve('dart_home/');
await Directory.fromUri(dartDataHome).create();
final binDir = Directory.fromUri(dartDataHome.resolve('install/bin'));
final environment = {
_dartDirectoryEnvKey: dartDataHome.toFilePath(),
'PATH':
'${binDir.path}$_pathEnvVarSeparator${Platform.environment['PATH']!}',
};
// 3. First run - should build
final firstRunResult = await _runDartdev(
fromDartdevSource,
'run',
arguments,
null,
environment,
);
expect(firstRunResult.stdout, contains('Hello World'));
expect(firstRunResult.exitCode, 0);
expect(firstRunResult.stderr, contains('Generated: '));
// No hooks.
expect(firstRunResult.stderr, isNot(contains('Running build hooks')));
expect(firstRunResult.stderr, isNot(contains('Running link hooks')));
// 4. Second run - should be cached
final secondRunResult = await _runDartdev(
fromDartdevSource,
'run',
arguments,
null,
environment,
);
expect(secondRunResult.stdout, contains('Hello World'));
expect(secondRunResult.exitCode, 0);
expect(secondRunResult.stdout, isNot(contains('Generated: ')));
});
});
for (final verbosityError in [true, false]) {
final testName = verbosityError ? ' --verbosity=error' : '';
test(
'dart run from git with build hook$testName',
timeout: longTimeout,
() async {
await inTempDir((tempUri) async {
const packageName = 'test_app_with_hook';
final (gitUri, gitRef) = await _setupGitRepoWithHook(
tempUri,
packageName: packageName,
);
final arguments = [
if (verbosityError) '--verbosity=error',
'test_app_with_hook@{git: {url: ${gitUri.toFilePath()}, ref: $gitRef}}',
'ignored',
'arguments',
];
final dartDataHome = tempUri.resolve('dart_home/');
await Directory.fromUri(dartDataHome).create();
final binDir = Directory.fromUri(dartDataHome.resolve('install/bin'));
final environment = {
_dartDirectoryEnvKey: dartDataHome.toFilePath(),
'PATH':
'${binDir.path}$_pathEnvVarSeparator${Platform.environment['PATH']!}',
};
final runResult = await _runDartdev(
fromDartdevSource,
'run',
arguments,
null,
environment,
);
expect(runResult.stdout, contains('Hello World'));
expect(runResult.exitCode, 0);
if (verbosityError) {
expect(runResult.stderr, isNot(contains('Running build hooks')));
expect(runResult.stderr, isNot(contains('Running link hooks')));
expect(runResult.stderr, isNot(contains('Generated: ')));
// Should have no other output than the program.
expect(runResult.stdout.trim(), equals('Hello World'));
} else {
expect(runResult.stderr, contains('Running build hooks'));
expect(runResult.stderr, contains('Running link hooks'));
expect(runResult.stderr, contains('Generated: '));
}
});
},
);
}
}
Future<(Uri gitUri, String gitRef)> _setupGitRepo(
Uri tempUri, {
String repoName = 'app.git',
required Map<String, String> files,
}) async {
final gitUri = tempUri.resolve('$repoName/');
for (final entry in files.entries) {
final file = File.fromUri(gitUri.resolve(entry.key));
await file.create(recursive: true);
await file.writeAsString(entry.value);
}
for (final commands in [
['init'],
['add', '.'],
['commit', '-m', '"Initial commit"'],
]) {
final gitResult = await Process.run(
'git',
commands,
workingDirectory: gitUri.toFilePath(),
);
if (gitResult.exitCode != 0) {
throw ProcessException('git', commands, gitResult.stderr);
}
}
final gitRef =
((await Process.run('git', [
'rev-parse',
'HEAD',
], workingDirectory: gitUri.toFilePath())).stdout
as String)
.trim();
return (gitUri, gitRef);
}
Future<(Uri gitUri, String gitRef)> _setupGitRepoWithHook(
Uri tempUri, {
String packageName = 'test_app_with_hook',
}) async {
return await _setupGitRepo(
tempUri,
repoName: '$packageName.git',
files: {
'pubspec.yaml': jsonEncode(
PubspecYamlFileSyntax(
name: packageName,
environment: EnvironmentSyntax(sdk: '^3.8.0'),
executables: {packageName: null},
dependencies: {
// Git dependencies can't have path dependencies outside the git repo
// so use a published dependency.
'hooks': HostedDependencySourceSyntax(version: '^1.0.0'),
},
).json,
),
'bin/$packageName.dart': '''
void main(List<String> args) {
print('Hello World');
}
''',
'hook/build.dart': '''
import 'package:hooks/hooks.dart';
void main(List<String> args) async {
await build(args, (input, output) async {
// Succeeds.
});
}
''',
'hook/link.dart': '''
import 'package:hooks/hooks.dart';
void main(List<String> args) async {
await link(args, (input, output) async {
// Succeeds.
});
}
''',
},
);
}
Future<(Uri gitUri, String gitRef)> _setupSimpleGitRepo(Uri tempUri) async {
return await _setupGitRepo(
tempUri,
files: {
'pubspec.yaml': await File.fromUri(
_package2Dir.uri.resolve('pubspec.yaml'),
).readAsString(),
'bin/dart_app.dart': await File.fromUri(
_package2Dir.uri.resolve('bin/dart_app.dart'),
).readAsString(),
'bin/other_app.dart': await File.fromUri(
_package2Dir.uri.resolve('bin/other_app.dart'),
).readAsString(),
},
);
}
final _dartDevEntryScriptUri = resolveDartDevUri('bin/dartdev.dart');
Future<RunProcessResult> _runDartdev(
bool fromDartdevSource,
String command,
List<String> arguments,
Uri? workingDirectory,
Map<String, String> environment, {
int expectedExitCode = 0,
}) async {
final installResult = await runDart(
arguments: [
if (fromDartdevSource) _dartDevEntryScriptUri.toFilePath(),
command,
...arguments,
],
workingDirectory: workingDirectory,
logger: logger,
environment: environment,
expectExitCodeZero: false,
);
expect(installResult.exitCode, equals(expectedExitCode));
return installResult;
}