blob: f3133555071eb655b13803f5f65c6edab44fb297 [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.
library analyzer_cli.test.driver;
import 'dart:io';
import 'package:analyzer/plugin/options.dart';
import 'package:analyzer/source/analysis_options_provider.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/error.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/plugin/plugin_configuration.dart';
import 'package:analyzer/src/services/lint.dart';
import 'package:analyzer_cli/src/bootloader.dart';
import 'package:analyzer_cli/src/driver.dart';
import 'package:analyzer_cli/src/options.dart';
import 'package:path/path.dart' as path;
import 'package:plugin/plugin.dart';
import 'package:unittest/unittest.dart';
import 'package:yaml/src/yaml_node.dart';
// TODO(pq): fix tests to run safely on the bots
// https://github.com/dart-lang/sdk/issues/25001
main() {}
_main() {
group('Driver', () {
StringSink savedOutSink, savedErrorSink;
int savedExitCode;
setUp(() {
savedOutSink = outSink;
savedErrorSink = errorSink;
savedExitCode = exitCode;
outSink = new StringBuffer();
errorSink = new StringBuffer();
});
tearDown(() {
outSink = savedOutSink;
errorSink = savedErrorSink;
exitCode = savedExitCode;
});
group('options', () {
test('custom processor', () {
Driver driver = new Driver();
TestProcessor processor = new TestProcessor();
driver.userDefinedPlugins = [new TestPlugin(processor)];
driver.start([
'--options',
'test/data/test_options.yaml',
'test/data/test_file.dart'
]);
expect(processor.options['test_plugin'], isNotNull);
expect(processor.exception, isNull);
});
});
group('exit codes', () {
StringSink savedOutSink, savedErrorSink;
int savedExitCode;
ExitHandler savedExitHandler;
setUp(() {
savedOutSink = outSink;
savedErrorSink = errorSink;
savedExitCode = exitCode;
savedExitHandler = exitHandler;
exitHandler = (code) => exitCode = code;
outSink = new StringBuffer();
errorSink = new StringBuffer();
});
tearDown(() {
outSink = savedOutSink;
errorSink = savedErrorSink;
exitCode = savedExitCode;
exitHandler = savedExitHandler;
});
test('fatal hints', () {
drive('test/data/file_with_hint.dart', args: ['--fatal-hints']);
expect(exitCode, 3);
});
test('not fatal hints', () {
drive('test/data/file_with_hint.dart');
expect(exitCode, 0);
});
test('fatal errors', () {
drive('test/data/file_with_error.dart');
expect(exitCode, 3);
});
test('not fatal warnings', () {
drive('test/data/file_with_warning.dart');
expect(exitCode, 0);
});
test('fatal warnings', () {
drive('test/data/file_with_warning.dart', args: ['--fatal-warnings']);
expect(exitCode, 3);
});
test('missing options file', () {
drive('test/data/test_file.dart', options: 'test/data/NO_OPTIONS_HERE');
expect(exitCode, 3);
});
test('missing dart file', () {
drive('test/data/NO_DART_FILE_HERE.dart');
expect(exitCode, 3);
});
test('part file', () {
drive('test/data/library_and_parts/part2.dart');
expect(exitCode, 3);
});
test('non-dangling part file', () {
Driver driver = new Driver();
driver.start([
'test/data/library_and_parts/lib.dart',
'test/data/library_and_parts/part1.dart',
]);
expect(exitCode, 0);
});
test('extra part file', () {
Driver driver = new Driver();
driver.start([
'test/data/library_and_parts/lib.dart',
'test/data/library_and_parts/part1.dart',
'test/data/library_and_parts/part2.dart',
]);
expect(exitCode, 3);
});
});
group('linter', () {
group('lints in options', () {
StringSink savedOutSink;
Driver driver;
setUp(() {
savedOutSink = outSink;
outSink = new StringBuffer();
driver = new Driver();
driver.start([
'--options',
'test/data/linter_project/.analysis_options',
'--lints',
'test/data/linter_project/test_file.dart'
]);
});
tearDown(() {
outSink = savedOutSink;
});
test('gets analysis options', () {
/// Lints should be enabled.
expect(driver.context.analysisOptions.lint, isTrue);
/// The .analysis_options file only specifies 'camel_case_types'.
var lintNames = getLints(driver.context).map((r) => r.name);
expect(lintNames, orderedEquals(['camel_case_types']));
});
test('generates lints', () {
expect(outSink.toString(),
contains('[lint] Name types using UpperCamelCase.'));
});
});
group('default lints', () {
StringSink savedOutSink;
Driver driver;
setUp(() {
savedOutSink = outSink;
outSink = new StringBuffer();
driver = new Driver();
driver.start([
'--lints',
'test/data/linter_project/test_file.dart',
'--options',
'test/data/linter_project/.analysis_options'
]);
});
tearDown(() {
outSink = savedOutSink;
});
test('gets default lints', () {
/// Lints should be enabled.
expect(driver.context.analysisOptions.lint, isTrue);
/// Default list should include camel_case_types.
var lintNames = getLints(driver.context).map((r) => r.name);
expect(lintNames, contains('camel_case_types'));
});
test('generates lints', () {
expect(outSink.toString(),
contains('[lint] Name types using UpperCamelCase.'));
});
});
group('no `--lints` flag (none in options)', () {
StringSink savedOutSink;
Driver driver;
setUp(() {
savedOutSink = outSink;
outSink = new StringBuffer();
driver = new Driver();
driver.start([
'test/data/no_lints_project/test_file.dart',
'--options',
'test/data/no_lints_project/.analysis_options'
]);
});
tearDown(() {
outSink = savedOutSink;
});
test('lints disabled', () {
expect(driver.context.analysisOptions.lint, isFalse);
});
test('no registered lints', () {
expect(getLints(driver.context), isEmpty);
});
test('no generated warnings', () {
expect(outSink.toString(), contains('No issues found'));
});
});
});
test('containsLintRuleEntry', () {
Map<String, YamlNode> options;
options = parseOptions('''
linter:
rules:
- foo
''');
expect(containsLintRuleEntry(options), true);
options = parseOptions('''
''');
expect(containsLintRuleEntry(options), false);
options = parseOptions('''
linter:
rules:
# - foo
''');
expect(containsLintRuleEntry(options), true);
options = parseOptions('''
linter:
# rules:
# - foo
''');
expect(containsLintRuleEntry(options), false);
});
group('options processing', () {
group('error filters', () {
StringSink savedOutSink;
Driver driver;
setUp(() {
savedOutSink = outSink;
outSink = new StringBuffer();
driver = new Driver();
driver.start([
'test/data/options_tests_project/test_file.dart',
'--options',
'test/data/options_tests_project/.analysis_options'
]);
});
tearDown(() {
outSink = savedOutSink;
});
test('filters', () {
var filters =
driver.context.getConfigurationData(CONFIGURED_ERROR_FILTERS);
expect(filters, hasLength(1));
var unused_error = new AnalysisError(
new TestSource(), 0, 1, HintCode.UNUSED_LOCAL_VARIABLE, [
['x']
]);
expect(filters.any((filter) => filter(unused_error)), isTrue);
});
test('language config', () {
expect(driver.context.analysisOptions.enableSuperMixins, isTrue);
});
});
});
group('in temp directory', () {
StringSink savedOutSink, savedErrorSink;
int savedExitCode;
Directory savedCurrentDirectory;
Directory tempDir;
setUp(() {
savedOutSink = outSink;
savedErrorSink = errorSink;
savedExitCode = exitCode;
outSink = new StringBuffer();
errorSink = new StringBuffer();
savedCurrentDirectory = Directory.current;
tempDir = Directory.systemTemp.createTempSync('analyzer_');
});
tearDown(() {
outSink = savedOutSink;
errorSink = savedErrorSink;
exitCode = savedExitCode;
Directory.current = savedCurrentDirectory;
tempDir.deleteSync(recursive: true);
});
test('packages folder', () {
Directory.current = tempDir;
new File(path.join(tempDir.path, 'test.dart')).writeAsStringSync('''
import 'package:foo/bar.dart';
main() {
baz();
}
''');
Directory packagesDir =
new Directory(path.join(tempDir.path, 'packages'));
packagesDir.createSync();
Directory fooDir = new Directory(path.join(packagesDir.path, 'foo'));
fooDir.createSync();
new File(path.join(fooDir.path, 'bar.dart')).writeAsStringSync('''
void baz() {}
''');
new Driver().start(['test.dart']);
expect(exitCode, 0);
});
test('no package resolution', () {
Directory.current = tempDir;
new File(path.join(tempDir.path, 'test.dart')).writeAsStringSync('''
import 'package:path/path.dart';
main() {}
''');
new Driver().start(['test.dart']);
expect(exitCode, 3);
String stdout = outSink.toString();
expect(stdout, contains('[error] Target of URI does not exist'));
expect(stdout, contains('1 error found.'));
expect(errorSink.toString(), '');
});
test('bad package root', () {
new Driver().start(['--package-root', 'does/not/exist', 'test.dart']);
String stdout = outSink.toString();
expect(exitCode, 3);
expect(
stdout,
contains(
'Package root directory (does/not/exist) does not exist.'));
});
});
});
group('Bootloader', () {
group('plugin processing', () {
StringSink savedErrorSink;
setUp(() {
savedErrorSink = errorSink;
errorSink = new StringBuffer();
});
tearDown(() {
errorSink = savedErrorSink;
});
test('bad format', () {
BootLoader loader = new BootLoader();
loader.createImage([
'--options',
'test/data/bad_plugin_options.yaml',
'test/data/test_file.dart'
]);
expect(
errorSink.toString(),
equals('Plugin configuration skipped: Unrecognized plugin config '
'format, expected `YamlMap`, got `YamlList` '
'(line 2, column 4)\n'));
});
test('plugin config', () {
BootLoader loader = new BootLoader();
Image image = loader.createImage([
'--options',
'test/data/plugin_options.yaml',
'test/data/test_file.dart'
]);
var plugins = image.config.plugins;
expect(plugins, hasLength(1));
expect(plugins.first.name, equals('my_plugin1'));
});
group('plugin validation', () {
test('requires class name', () {
expect(
validate(new PluginInfo(
name: 'test_plugin', libraryUri: 'my_package/foo.dart')),
isNotNull);
});
test('requires library URI', () {
expect(
validate(
new PluginInfo(name: 'test_plugin', className: 'MyPlugin')),
isNotNull);
});
test('check', () {
expect(
validate(new PluginInfo(
name: 'test_plugin',
className: 'MyPlugin',
libraryUri: 'my_package/foo.dart')),
isNull);
});
});
});
});
}
const emptyOptionsFile = 'test/data/empty_options.yaml';
/// Start a driver for the given [source], optionally providing additional
/// [args] and an [options] file path. The value of [options] defaults to
/// an empty options file to avoid unwanted configuration from an otherwise
/// discovered options file.
void drive(String source,
{String options: emptyOptionsFile,
List<String> args: const <String>[]}) =>
new Driver().start(['--options', options, source]..addAll(args));
Map<String, YamlNode> parseOptions(String src) =>
new AnalysisOptionsProvider().getOptionsFromString(src);
class TestPlugin extends Plugin {
TestProcessor processor;
TestPlugin(this.processor);
@override
String get uniqueIdentifier => 'test_plugin.core';
@override
void registerExtensionPoints(RegisterExtensionPoint register) {
// None
}
@override
void registerExtensions(RegisterExtension register) {
register(OPTIONS_PROCESSOR_EXTENSION_POINT_ID, processor);
}
}
class TestProcessor extends OptionsProcessor {
Map<String, YamlNode> options;
Exception exception;
@override
void onError(Exception exception) {
this.exception = exception;
}
@override
void optionsProcessed(
AnalysisContext context, Map<String, YamlNode> options) {
this.options = options;
}
}
class TestSource implements Source {
TestSource();
@override
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}