blob: 7a779e58b3e24bd32ac67095e2aed36e330edb64 [file] [log] [blame] [edit]
// Copyright (c) 2024, 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.
/// Entrypoint to run dynamic module tests.
library;
import 'dart:io';
import 'package:args/args.dart';
import 'aot.dart';
import 'ddc.dart';
import 'load.dart';
import 'model.dart';
import 'target.dart';
import 'util.dart';
void main(List<String> args) async {
final suiteRoot = repoRoot.resolve('pkg/dynamic_modules/test/data/');
var tests = loadAllTests(suiteRoot);
final parser = ArgParser()
..addFlag('help', help: 'Help message', negatable: false, abbr: 'h')
..addOption('test',
help: 'Run a single test, rather than the entire suite',
allowed: tests.map((t) => t.name).toList(),
abbr: 't')
..addOption('runtime',
help: 'Which runtime to use to run the test configuration',
allowed: Target.values.map((v) => v.name),
defaultsTo: Target.ddc.name,
abbr: 'r')
..addOption('configuration',
help: 'Configuration to use for reporting test results', abbr: 'n')
..addOption('output-directory',
help: 'location where to emit the json-l result and log files')
..addFlag('verbose',
help: 'Show a lot of information', negatable: false, abbr: 'v');
final options = parser.parse(args);
if (options['help'] as bool) {
print(parser.usage);
return;
}
var singleTest = options['test'] as String?;
if (singleTest != null) {
tests = [...tests.where((t) => t.name == singleTest)];
}
final logger = Logger(options['verbose'] as bool);
final target = Target.values.firstWhere((v) => v.name == options['runtime']);
late TargetExecutor executor;
try {
executor = switch (target) {
Target.ddc => DdcExecutor(logger),
Target.aot => AotExecutor(logger),
Target.dart2wasm => UnimplementedExecutor(logger),
};
final results = <DynamicModuleTestResult>[];
for (final t in tests) {
results.add(await _runSingleTest(t, executor));
}
final result = _reportResults(results,
writeLog: singleTest == null,
configuration: options['configuration'],
logDir: options['output-directory']);
if (result != 0) {
exitCode = result;
}
} finally {
executor.suiteComplete();
}
}
/// Takes the steps to build the artifacts needed by a test and then execute it
/// on the target environment.
Future<DynamicModuleTestResult> _runSingleTest(
DynamicModuleTest test, TargetExecutor target) async {
var timer = Stopwatch()..start();
try {
await target.compileApplication(test);
for (var name in test.dynamicModules.keys) {
await target.compileDynamicModule(test, name);
}
} catch (e, st) {
return DynamicModuleTestResult.compileError(test, '$e\n$st', timer.elapsed);
}
try {
await target.executeApplication(test);
} catch (e, st) {
return DynamicModuleTestResult.runtimeError(test, '$e\n$st', timer.elapsed);
}
return DynamicModuleTestResult.pass(test, timer.elapsed);
}
/// Generates a report of the test results in the JSON format
/// that is expected by our testing infrastructure.
int _reportResults(
List<DynamicModuleTestResult> results, {
required bool writeLog,
String? configuration,
String? logDir,
}) {
bool fail = false;
print('Test results:');
for (var result in results) {
print(' ${result.name}: ${result.status}');
if (result.status != Status.pass) fail = true;
}
if (fail) print('Error: some tests failed');
if (writeLog) {
if (logDir == null) {
print('Error: no output directory provided, logs won\'t be emitted.');
return 1;
}
if (configuration == null) {
print('Error: no configuration name provided, logs won\'t be emitted.');
return 1;
}
// Ensure the directory URI ends with a path separator.
var dirUri = Directory(logDir).uri;
File.fromUri(dirUri.resolve('results.json')).writeAsStringSync(
results.map((r) => '${r.toRecordJson(configuration)}\n').join(),
flush: true);
File.fromUri(dirUri.resolve('logs.json')).writeAsStringSync(
results
.where((r) => r.status != Status.pass)
.map((r) => '${r.toLogJson(configuration)}\n')
.join(),
flush: true);
print('Success: log files emitted under $dirUri');
} else if (fail) {
return 1;
}
return 0;
}
/// Placeholder until we implement all executors.
class UnimplementedExecutor implements TargetExecutor {
UnimplementedExecutor(Logger logger);
@override
Future<void> suiteComplete() async => throw UnimplementedError();
@override
Future compileApplication(DynamicModuleTest test) async =>
throw UnimplementedError();
@override
Future compileDynamicModule(DynamicModuleTest test, String name) async =>
throw UnimplementedError();
@override
Future executeApplication(DynamicModuleTest test) async =>
throw UnimplementedError();
}