blob: f3749f0bfca6c96d94090cf451c5fd95e6086733 [file] [log] [blame]
// Copyright (c) 2015, 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:io';
import 'package:async/async.dart';
import 'package:path/path.dart' as p;
import 'package:source_span/source_span.dart';
import 'package:stack_trace/stack_trace.dart';
import 'package:test_api/src/utils.dart'; // ignore: implementation_imports
import 'runner/application_exception.dart';
import 'runner/configuration.dart';
import 'runner/version.dart';
import 'runner.dart';
import 'util/exit_codes.dart' as exit_codes;
import 'util/io.dart';
StreamSubscription signalSubscription;
bool isShutdown = false;
/// Returns the path to the global test configuration file.
final String _globalConfigPath = () {
if (Platform.environment.containsKey('DART_TEST_CONFIG')) {
return Platform.environment['DART_TEST_CONFIG'];
} else if (Platform.operatingSystem == 'windows') {
return p.join(Platform.environment['LOCALAPPDATA'], 'DartTest.yaml');
} else {
return '${Platform.environment['HOME']}/.dart_test.yaml';
}
}();
Future<void> main(List<String> args) async {
await _execute(args);
completeShutdown();
}
Future<void> runTests(List<String> args) async {
await _execute(args);
}
void completeShutdown() {
if (isShutdown) return;
if (signalSubscription != null) {
signalSubscription.cancel();
signalSubscription = null;
}
isShutdown = true;
stdinLines.cancel(immediate: true);
}
Future<void> _execute(List<String> args) async {
/// A merged stream of all signals that tell the test runner to shut down
/// gracefully.
///
/// Signals will only be captured as long as this has an active subscription.
/// Otherwise, they'll be handled by Dart's default signal handler, which
/// terminates the program immediately.
final _signals = Platform.isWindows
? ProcessSignal.sigint.watch()
: StreamGroup.merge(
[ProcessSignal.sigterm.watch(), ProcessSignal.sigint.watch()]);
Configuration configuration;
try {
configuration = Configuration.parse(args);
} on FormatException catch (error) {
_printUsage(error.message);
exitCode = exit_codes.usage;
return;
}
if (configuration.help) {
_printUsage();
return;
}
if (configuration.version) {
var version = testVersion;
if (version == null) {
stderr.writeln("Couldn't find version number.");
exitCode = exit_codes.data;
} else {
print(version);
}
return;
}
try {
var fileConfiguration = Configuration.empty;
if (File(_globalConfigPath).existsSync()) {
fileConfiguration = fileConfiguration
.merge(Configuration.load(_globalConfigPath, global: true));
}
if (File(configuration.configurationPath).existsSync()) {
fileConfiguration = fileConfiguration
.merge(Configuration.load(configuration.configurationPath));
}
configuration = fileConfiguration.merge(configuration);
} on SourceSpanFormatException catch (error) {
stderr.writeln(error.toString(color: configuration.color));
exitCode = exit_codes.data;
return;
} on FormatException catch (error) {
stderr.writeln(error.message);
exitCode = exit_codes.data;
return;
} on IOException catch (error) {
stderr.writeln(error.toString());
exitCode = exit_codes.noInput;
return;
}
var undefinedPresets = configuration.chosenPresets
.where((preset) => !configuration.knownPresets.contains(preset))
.toList();
if (undefinedPresets.isNotEmpty) {
_printUsage("Undefined ${pluralize('preset', undefinedPresets.length)} "
"${toSentence(undefinedPresets.map((preset) => '"$preset"'))}.");
exitCode = exit_codes.usage;
return;
}
if (!configuration.explicitPaths &&
!Directory(configuration.paths.single).existsSync()) {
_printUsage('No test files were passed and the default "test/" '
"directory doesn't exist.");
exitCode = exit_codes.data;
return;
}
Runner runner;
signalSubscription ??= _signals.listen((signal) async {
completeShutdown();
await runner?.close();
});
try {
runner = Runner(configuration);
exitCode = (await runner.run()) ? 0 : 1;
} on ApplicationException catch (error) {
stderr.writeln(error.message);
exitCode = exit_codes.data;
} on SourceSpanFormatException catch (error) {
stderr.writeln(error.toString(color: configuration.color));
exitCode = exit_codes.data;
} on FormatException catch (error) {
stderr.writeln(error.message);
exitCode = exit_codes.data;
} catch (error, stackTrace) {
stderr.writeln(getErrorMessage(error));
stderr.writeln(Trace.from(stackTrace).terse);
stderr.writeln('This is an unexpected error. Please file an issue at '
'http://github.com/dart-lang/test\n'
'with the stack trace and instructions for reproducing the error.');
exitCode = exit_codes.software;
} finally {
await runner?.close();
}
// TODO(grouma) - figure out why the executable can hang in the travis
// environment. https://github.com/dart-lang/test/issues/599
if (Platform.environment['FORCE_TEST_EXIT'] == 'true') {
exit(exitCode);
}
return;
}
/// Print usage information for this command.
///
/// If [error] is passed, it's used in place of the usage message and the whole
/// thing is printed to stderr instead of stdout.
void _printUsage([String error]) {
var output = stdout;
var message = 'Runs tests in this package.';
if (error != null) {
message = error;
output = stderr;
}
output.write('''${wordWrap(message)}
Usage: pub run test [files or directories...]
${Configuration.usage}
''');
}