blob: 352dffb2c83b0081bc5a89b7c9682dcb8cd02361 [file] [edit]
// Copyright (c) 2026, 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 'dart:isolate';
import 'package:dartdev/src/commands/run.dart';
import 'package:dartdev/src/eval_packages.dart';
import 'package:dartdev/src/utils.dart';
import 'package:dartdev/src/vm_interop_handler.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
void main() {
group('eval option parsing', () {
test(
'globalDartdevOptionsParser includes -e, -P / --package-constraint, --offline',
() {
final parser = globalDartdevOptionsParser();
expect(parser.options.containsKey(evalOption), isTrue);
expect(parser.options[evalOption]?.abbr, equals('e'));
expect(parser.options.containsKey(packageConstraintOption), isTrue);
expect(parser.options[packageConstraintOption]?.abbr, equals('P'));
expect(parser.options.containsKey(offlineOption), isTrue);
expect(parser.options.containsKey('package'), isFalse);
expect(parser.options.containsKey('with'), isFalse);
},
);
test(
'RunCommand argParser includes -e, -P / --package-constraint, --offline',
() {
final runCmd = RunCommand();
expect(runCmd.argParser.options.containsKey(evalOption), isTrue);
expect(runCmd.argParser.options[evalOption]?.abbr, equals('e'));
expect(
runCmd.argParser.options.containsKey(packageConstraintOption),
isTrue,
);
expect(
runCmd.argParser.options[packageConstraintOption]?.abbr,
equals('P'),
);
expect(runCmd.argParser.options.containsKey(offlineOption), isTrue);
expect(runCmd.argParser.options.containsKey('package'), isFalse);
expect(runCmd.argParser.options.containsKey('with'), isFalse);
},
);
test('globalDartdevOptionsParser parses -e and --eval flags', () {
final parser = globalDartdevOptionsParser();
final results1 = parser.parse(const ['-e', 'void main() {}']);
expect(results1.wasParsed(evalOption), isTrue);
expect(results1.option(evalOption), equals('void main() {}'));
final results2 = parser.parse(const ['--eval=void main() {}']);
expect(results2.wasParsed(evalOption), isTrue);
expect(results2.option(evalOption), equals('void main() {}'));
});
test(
'globalDartdevOptionsParser parses -P and --package-constraint flags',
() {
final parser = globalDartdevOptionsParser();
final results = parser.parse(const [
'-P',
'http',
'-P',
'path:^1.8.0',
'-e',
'void main() {}',
]);
expect(results.wasParsed(packageConstraintOption), isTrue);
expect(
results.multiOption(packageConstraintOption),
equals(const ['http', 'path:^1.8.0']),
);
},
);
test('RunCommand argParser parses -e, -P, --offline, and arguments', () {
final runCmd = RunCommand();
final results = runCmd.argParser.parse(const [
'-P',
'http',
'--offline',
'-e',
'void main() {}',
'arg1',
'arg2',
]);
expect(results.wasParsed(evalOption), isTrue);
expect(results.option(evalOption), equals('void main() {}'));
expect(results.wasParsed(packageConstraintOption), isTrue);
expect(
results.multiOption(packageConstraintOption),
equals(const ['http']),
);
expect(results.wasParsed(offlineOption), isTrue);
expect(results.flag(offlineOption), isTrue);
expect(results.rest, equals(const ['arg1', 'arg2']));
});
});
group('PackageUriExtension', () {
test('isPackage correctly identifies valid package URIs', () {
expect(Uri.parse('package:http/http.dart').isPackage, isTrue);
expect(Uri.parse('package:path').isPackage, isTrue);
expect(Uri.parse('package:').isPackage, isFalse);
expect(Uri.parse('package:/').isPackage, isFalse);
expect(Uri.parse('dart:io').isPackage, isFalse);
expect(Uri.parse('file:///foo/bar.dart').isPackage, isFalse);
expect(Uri.parse('https://example.com').isPackage, isFalse);
});
test('packageName extracts package name from package URIs', () {
expect(
Uri.parse('package:http/http.dart').packageName,
equals('http'),
);
expect(
Uri.parse('package:path').packageName,
equals('path'),
);
expect(Uri.parse('package:').packageName, isNull);
expect(Uri.parse('package:/').packageName, isNull);
expect(Uri.parse('dart:io').packageName, isNull);
expect(Uri.parse('file:///foo/bar.dart').packageName, isNull);
});
});
group('EvalPackageResolver AST import extraction', () {
test('extracts package imports using Dart AST parser', () {
const code = '''
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:path/path.dart' show join;
// import 'package:commented_out/commented_out.dart';
/*
import 'package:block_commented/block.dart';
*/
void main() {
const fakeImport = "import 'package:in_string/in_string.dart'";
print(fakeImport);
}
''';
final imports = EvalPackageResolver.extractPackageImports(code);
expect(imports, equals(const {'http', 'path'}));
});
});
group('EvalPackageResolver package config resolution', () {
late Directory tempDir;
setUp(() {
tempDir = Directory.systemTemp.createTempSync('dartdev_eval_test_');
});
tearDown(() {
if (tempDir.existsSync()) {
tempDir.deleteSync(recursive: true);
}
});
test('throws StateError when localPackageConfig does not exist', () async {
final nonexistent = path.join(tempDir.path, 'missing_config.json');
await expectLater(
EvalPackageResolver.resolvePackageConfig(
'import "package:foo/foo.dart"; void main() {}',
localPackageConfig: nonexistent,
),
throwsA(
isA<StateError>().having(
(e) => e.message,
'message',
contains('Specified package configuration file does not exist'),
),
),
);
});
test(
'throws StateError when localPackageConfig has invalid JSON',
() async {
final invalidFile = File(path.join(tempDir.path, 'bad_config.json'))
..writeAsStringSync('{ malformed json');
await expectLater(
EvalPackageResolver.resolvePackageConfig(
'import "package:foo/foo.dart"; void main() {}',
localPackageConfig: invalidFile.path,
),
throwsA(
isA<StateError>().having(
(e) => e.message,
'message',
contains('Failed to parse package configuration file'),
),
),
);
},
);
test(
'reuses localPackageConfig when no explicit constraints and packages present',
() async {
final configFile = File(path.join(tempDir.path, 'valid_config.json'))
..writeAsStringSync(
jsonEncode({
'configVersion': 2,
'packages': [
{'name': 'http', 'rootUri': 'file:///mock/http'},
],
}),
);
final result = await EvalPackageResolver.resolvePackageConfig(
'import "package:http/http.dart"; void main() {}',
localPackageConfig: configFile.path,
);
expect(result, equals(configFile.path));
},
);
});
group('RunCommand.runEval execution', () {
late ReceivePort receivePort;
late StreamController<List<dynamic>> vmInteropEvents;
setUp(() {
vmInteropEvents = StreamController<List<dynamic>>();
receivePort = ReceivePort()
..listen((msg) {
if (msg is List) {
vmInteropEvents.add(msg);
}
});
VmInteropHandler.initialize(receivePort.sendPort);
});
tearDown(() {
receivePort.close();
vmInteropEvents.close();
VmInteropHandler.initialize(null);
});
test(
'runEval invokes VmInteropHandler with data URI and useExecProcess',
() async {
const code = 'void main(List<String> args) { print("hello"); }';
final runCmd = RunCommand();
final args = runCmd.argParser.parse(
const ['-e', code, 'arg1', 'arg2'],
);
final exitCode = await runCmd.runEval(args);
expect(exitCode, equals(0));
final message = await vmInteropEvents.stream.first;
expect(message[0], equals(2));
final scriptUri = Uri.parse(message[1] as String);
expect(scriptUri.scheme, equals('data'));
expect(scriptUri.data?.mimeType, equals(dartMimeType));
expect(scriptUri.data?.contentAsString(encoding: utf8), equals(code));
expect(message[5], equals(const ['arg1', 'arg2']));
},
);
test(
'runEval preserves multiline code verbatim without modification',
() async {
const multilineCode = '''
import 'dart:math';
class Calculator {
int add(int a, int b) => a + b;
}
void main(List<String> args) {
final calc = Calculator();
print(calc.add(2, 3));
}
''';
final runCmd = RunCommand();
final args = runCmd.argParser.parse(const ['-e', multilineCode]);
final exitCode = await runCmd.runEval(args);
expect(exitCode, equals(0));
final message = await vmInteropEvents.stream.first;
expect(message[0], equals(2));
final scriptUri = Uri.parse(message[1] as String);
expect(scriptUri.scheme, equals('data'));
expect(
scriptUri.data?.contentAsString(encoding: utf8),
equals(multilineCode),
);
},
);
test('runEval respects explicit --packages option', () async {
const code = 'void main() {}';
final runCmd = RunCommand();
final args = runCmd.argParser.parse(const [
'--packages=/custom/path/package_config.json',
'-e',
code,
]);
final exitCode = await runCmd.runEval(args);
expect(exitCode, equals(0));
final message = await vmInteropEvents.stream.first;
expect(message[0], equals(2));
expect(message[3], equals('/custom/path/package_config.json'));
});
test('runEval returns errorExitCode when code is empty', () async {
final runCmd = RunCommand();
final args = runCmd.argParser.parse(const ['-e', '']);
final exitCode = await runCmd.runEval(args);
expect(exitCode, equals(RunCommand.errorExitCode));
});
});
}