blob: 61ea61782bacbb3e138f542ee2fad2278f612e71 [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:io';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/source/error_processor.dart';
import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/summary2/package_bundle_format.dart';
import 'package:analyzer/src/util/sdk.dart';
import 'package:analyzer_cli/src/ansi.dart' as ansi;
import 'package:analyzer_cli/src/driver.dart';
import 'package:analyzer_cli/src/options.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'package:yaml/src/yaml_node.dart';
import 'utils.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(BuildModeTest);
defineReflectiveTests(BuildModeSummaryDependenciesTest);
defineReflectiveTests(ExitCodesTest);
defineReflectiveTests(LinterTest);
defineReflectiveTests(NonDartFilesTest);
defineReflectiveTests(OptionsTest);
}, name: 'Driver');
}
class AbstractBuildModeTest extends BaseTest {
List<String> get _sdkSummaryArguments {
var sdkPath = path.dirname(
path.dirname(
Platform.resolvedExecutable,
),
);
var dartSdkSummaryPath = path.join(
sdkPath,
'lib',
'_internal',
'strong.sum',
);
return ['--dart-sdk-summary', dartSdkSummaryPath];
}
Future<void> _doDrive(
String filePath, {
String sourceArgument,
String fileUri,
List<String> additionalArgs = const [],
}) async {
filePath = _posixToPlatformPath(filePath);
var optionsFileName = AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE;
var options =
_posixToPlatformPath('data/options_tests_project/' + optionsFileName);
var args = <String>[];
args.add('--build-mode');
args.add('--format=machine');
args.addAll(_sdkSummaryArguments);
args.addAll(additionalArgs);
if (sourceArgument == null) {
fileUri ??= 'file:///test_file.dart';
sourceArgument = '$fileUri|$filePath';
}
await drive(sourceArgument, args: args, options: options);
}
}
class BaseTest {
static const emptyOptionsFile = 'data/empty_options.yaml';
StringSink _savedOutSink, _savedErrorSink;
int _savedExitCode;
ExitHandler _savedExitHandler;
Driver driver;
AnalysisOptions get analysisOptions => driver.analysisDriver.analysisOptions;
/// Normalize text with bullets.
String bulletToDash(StringSink item) => '$item'.replaceAll('•', '-');
/// 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.
Future<void> drive(
String source, {
String options = emptyOptionsFile,
List<String> args = const <String>[],
}) {
return driveMany([source], options: options, args: args);
}
/// Like [drive], but takes an array of sources.
Future<void> driveMany(
List<String> sources, {
String options = emptyOptionsFile,
List<String> args = const <String>[],
}) async {
options = _posixToPlatformPath(options);
driver = Driver();
var cmd = <String>[];
if (options != null) {
cmd = <String>[
'--options',
path.join(testDirectory, options),
];
}
cmd..addAll(sources.map(_adjustFileSpec))..addAll(args);
await driver.start(cmd);
}
void setUp() {
ansi.runningTests = true;
_savedOutSink = outSink;
_savedErrorSink = errorSink;
_savedExitHandler = exitHandler;
_savedExitCode = exitCode;
exitHandler = (code) => exitCode = code;
outSink = StringBuffer();
errorSink = StringBuffer();
}
void tearDown() {
outSink = _savedOutSink;
errorSink = _savedErrorSink;
exitCode = _savedExitCode;
exitHandler = _savedExitHandler;
ansi.runningTests = false;
}
/// Convert a file specification from a relative path to an absolute path.
/// Handles the case where the file specification is of the form "$uri|$path".
String _adjustFileSpec(String fileSpec) {
var uriPrefixLength = fileSpec.indexOf('|') + 1;
var uriPrefix = fileSpec.substring(0, uriPrefixLength);
var relativePath = fileSpec.substring(uriPrefixLength);
return '$uriPrefix${path.join(testDirectory, relativePath)}';
}
/// Convert the given posix [filePath] to conform to to the platform style.
///
/// This is a utility method for testing; paths passed in to other methods in
/// this class are never converted automatically.
String _posixToPlatformPath(String filePath) {
if (filePath == null) {
return null;
}
if (path.style == path.windows.style) {
filePath = filePath.replaceAll(
path.posix.separator,
path.windows.separator,
);
}
return filePath;
}
}
@reflectiveTest
class BuildModeSummaryDependenciesTest extends AbstractBuildModeTest {
String tempDir;
/// Any direct export is a dependency.
Future<void> test_export_direct() async {
await _withTempDir(() async {
var a = await _buildPackage('a', [], 'class A {}');
await _assertDependencies('c', [a], '''
export 'package:a/a.dart';
''', [a]);
});
}
/// Imports of dependencies are not necessary dependencies.
/// Here our dependency does not use its dependency.
Future<void> test_import2_notUsed() async {
await _withTempDir(() async {
var a = await _buildPackage('a', [], '');
var b = await _buildPackage('b', [a], '''
import 'package:a/a.dart';
''');
await _assertDependencies('c', [a, b], '''
import 'package:b/b.dart';
''', [b]);
});
}
Future<void> test_import2_usedAsFieldType() async {
await _withTempDir(() async {
var a = await _buildPackage('a', [], 'class A {}');
var b = await _buildPackage('b', [a], '''
import 'package:a/a.dart';
class B {
A f;
}
''');
// We don't use `f`, so don't depend on "a".
await _assertDependencies('c', [a, b], '''
import 'package:b/b.dart';
var x = B();
''', [b]);
// We use `f` for type inference.
// So, dependency on "a".
await _assertDependencies('c', [a, b], '''
import 'package:b/b.dart';
var x = B().f;
''', [a, b]);
// We reference `f` in initializer, but not for type inference.
// So, no dependency on "a".
await _assertDependencies('c', [a, b], '''
import 'package:b/b.dart';
Object x = B().f;
''', [b]);
// We perform full analysis, so request the type of `f`;
// So, dependency on "a".
await _assertDependencies(
'c',
[a, b],
'''
import 'package:b/b.dart';
Object x = B().f;
''',
[a, b],
summaryOnly: false,
);
});
}
Future<void> test_import2_usedAsSupertype() async {
await _withTempDir(() async {
var a = await _buildPackage('a', [], 'class A {}');
var b = await _buildPackage('b', [], 'class B {}');
var c = await _buildPackage('c', [a], '''
import 'package:a/a.dart';
import 'package:b/b.dart';
class C1 extends A {}
class C2 extends B {}
''');
// When we instantiate `C1`, we ask `C1` for its type parameters.
// So, we apply resolution to the whole `C1` header (not members).
// So, we request `A` that is the superclass of `C1`.
// So, dependency on "a".
//
// But we don't access `C2`, so don't use its supertype `B`.
// So, no dependency on "b".
await _assertDependencies('d', [a, b, c], '''
import 'package:c/c.dart';
C1 x;
''', [a, c]);
});
}
Future<void> test_import2_usedAsTopLevelVariableType() async {
await _withTempDir(() async {
var a = await _buildPackage('a', [], 'class A {}');
var b = await _buildPackage('b', [a], '''
import 'package:a/a.dart';
A v;
''');
// We don't use `v`.
// So, no dependency on "a".
await _assertDependencies('c', [a, b], '''
import 'package:b/b.dart';
''', [b]);
// We use `v` for type inference.
// So, dependency on "a".
await _assertDependencies('c', [a, b], '''
import 'package:b/b.dart';
var x = v;
''', [a, b]);
// We don't use `v` for type inference.
// So, no dependency on "a".
await _assertDependencies('c', [a, b], '''
import 'package:b/b.dart';
Object x = v;
''', [b]);
// We perform full analysis, and request the type of `v`.
// So, dependency on "a".
await _assertDependencies(
'c',
[a, b],
'''
import 'package:b/b.dart';
Object x = v;
''',
[a, b],
summaryOnly: false,
);
// We use `v` in a method body.
// So, no dependency on "a".
await _assertDependencies('c', [a, b], '''
import 'package:b/b.dart';
main() {
v;
}
''', [b]);
// We perform full analysis, so ask for the type of `v`.
// So, dependency on "a".
await _assertDependencies(
'c',
[a, b],
'''
import 'package:b/b.dart';
main() {
v;
}
''',
[a, b],
summaryOnly: false,
);
});
}
/// Any direct import is a dependency.
Future<void> test_import_direct() async {
await _withTempDir(() async {
var a = await _buildPackage('a', [], '');
var b = await _buildPackage('b', [], '');
await _assertDependencies('c', [a, b], '''
import 'package:a/a.dart';
import 'package:b/b.dart';
''', [a, b]);
});
}
/// Exports of dependencies are dependencies.
Future<void> test_import_export() async {
await _withTempDir(() async {
var a = await _buildPackage('a', [], 'class A {}');
var b = await _buildPackage('b', [a], '''
export 'package:a/a.dart';
''');
await _assertDependencies('c', [a, b], '''
import 'package:b/b.dart';
''', [a, b]);
});
}
Future<void> _assertDependencies(
String name,
List<_DependencyPackage> inputPackages,
String content,
List<_DependencyPackage> expectedPackages, {
bool summaryOnly = true,
}) async {
var pkg = await _buildPackage(name, inputPackages, content,
summaryOnly: summaryOnly);
var depString = File(pkg.dep).readAsStringSync();
var expectedList = expectedPackages.map((p) => p.sum).toList();
expect(depString.split('\n'), unorderedEquals(expectedList));
}
Future<_DependencyPackage> _buildPackage(
String name,
List<_DependencyPackage> inputPackages,
String content, {
bool summaryOnly = true,
}) async {
var filePath = path.join(tempDir, '$name.dart');
File(filePath).writeAsStringSync(content);
var pkg = _DependencyPackage(
name: name,
path: filePath,
uri: 'package:$name/$name.dart',
sum: path.join(tempDir, '$name.sum'),
dep: path.join(tempDir, '$name.dep'),
);
var args = <String>[];
if (summaryOnly) {
args.add('--build-summary-only');
}
for (var input in inputPackages) {
args.add('--build-summary-input=${input.sum}');
}
args.add('--build-summary-output=${pkg.sum}');
args.add('--summary-deps-output=${pkg.dep}');
await _doDrive(pkg.path, fileUri: pkg.uri, additionalArgs: args);
expect(exitCode, 0);
return pkg;
}
Future<void> _withTempDir(Future<void> Function() f) async {
await withTempDirAsync((tempDir) async {
this.tempDir = tempDir;
await f();
});
}
}
@reflectiveTest
class BuildModeTest extends AbstractBuildModeTest {
Future<void> test_buildLinked() async {
await withTempDirAsync((tempDir) async {
var outputPath = path.join(tempDir, 'test_file.dart.sum');
await _doDrive(path.join('data', 'test_file.dart'), additionalArgs: [
'--build-summary-only',
'--build-summary-output=$outputPath'
]);
var output = File(outputPath);
expect(output.existsSync(), isTrue);
var bundle = PackageBundleReader(await output.readAsBytes());
var testFileUri = 'file:///test_file.dart';
expect(_linkedLibraryUriList(bundle), [testFileUri]);
expect(
_linkedLibraryUnitUriList(bundle, testFileUri),
[testFileUri],
);
expect(exitCode, 0);
});
}
Future<void> test_buildLinked_invalidPartUri() async {
await withTempDirAsync((tempDir) async {
var aDart = path.join(tempDir, 'a.dart');
var aUri = 'package:aaa/a.dart';
var aSum = path.join(tempDir, 'a.sum');
File(aDart).writeAsStringSync('''
part '[invalid]';
''');
await _doDrive(aDart,
fileUri: aUri, additionalArgs: ['--build-summary-output=$aSum']);
expect(exitCode, ErrorSeverity.ERROR.ordinal);
var bytes = File(aSum).readAsBytesSync();
var bundle = PackageBundleReader(bytes);
expect(_linkedLibraryUriList(bundle), [aUri]);
expect(_linkedLibraryUnitUriList(bundle, aUri), [aUri]);
});
}
Future<void> test_buildSuppressExitCode_fail_whenFileNotFound() async {
await _doDrive(path.join('data', 'non_existent_file.dart'),
additionalArgs: ['--build-suppress-exit-code']);
expect(exitCode, isNot(0));
}
Future<void> test_buildSuppressExitCode_success_evenIfHasError() async {
await _doDrive(path.join('data', 'file_with_error.dart'),
additionalArgs: ['--build-suppress-exit-code']);
expect(exitCode, 0);
}
Future<void> test_consumeLinked() async {
await withTempDirAsync((tempDir) async {
var aDart = path.join(tempDir, 'a.dart');
var bDart = path.join(tempDir, 'b.dart');
var cDart = path.join(tempDir, 'c.dart');
var aUri = 'package:aaa/a.dart';
var bUri = 'package:bbb/b.dart';
var cUri = 'package:ccc/c.dart';
var aSum = path.join(tempDir, 'a.sum');
var bSum = path.join(tempDir, 'b.sum');
var cSum = path.join(tempDir, 'c.sum');
File(aDart).writeAsStringSync('class A {}');
File(bDart).writeAsStringSync('''
export 'package:aaa/a.dart';
class B {}
''');
File(cDart).writeAsStringSync('''
import 'package:bbb/b.dart';
var a = new A();
var b = new B();
''');
// Analyze package:aaa/a.dart and compute summary.
{
await _doDrive(aDart,
fileUri: aUri, additionalArgs: ['--build-summary-output=$aSum']);
expect(exitCode, 0);
var bytes = File(aSum).readAsBytesSync();
var bundle = PackageBundleReader(bytes);
expect(_linkedLibraryUriList(bundle), [aUri]);
expect(_linkedLibraryUnitUriList(bundle, aUri), [aUri]);
}
// Analyze package:bbb/b.dart and compute summary.
{
await _doDrive(bDart, fileUri: bUri, additionalArgs: [
'--build-summary-input=$aSum',
'--build-summary-output=$bSum'
]);
expect(exitCode, 0);
var bytes = File(bSum).readAsBytesSync();
var bundle = PackageBundleReader(bytes);
expect(_linkedLibraryUriList(bundle), [bUri]);
expect(_linkedLibraryUnitUriList(bundle, bUri), [bUri]);
}
// Analyze package:ccc/c.dart and compute summary.
{
await _doDrive(cDart, fileUri: cUri, additionalArgs: [
'--build-summary-input=$aSum,$bSum',
'--build-summary-output=$cSum'
]);
expect(exitCode, 0);
var bytes = File(cSum).readAsBytesSync();
var bundle = PackageBundleReader(bytes);
expect(_linkedLibraryUriList(bundle), [cUri]);
expect(_linkedLibraryUnitUriList(bundle, cUri), [cUri]);
}
});
}
Future<void> test_error_notUriPipePath() async {
await withTempDirAsync((tempDir) async {
var testDart = path.join(tempDir, 'test.dart');
File(testDart).writeAsStringSync('var v = 42;');
// We pass just path, not "uri|path", this is a fatal error.
await _doDrive(
testDart,
additionalArgs: ['--build-mode', '--format=machine'],
sourceArgument: testDart,
);
expect(exitCode, ErrorSeverity.ERROR.ordinal);
});
}
Future<void> test_fail_whenHasError() async {
await _doDrive(path.join('data', 'file_with_error.dart'));
expect(exitCode, isNot(0));
}
Future<void> test_noInputs() async {
await withTempDirAsync((tempDir) async {
var outputPath = path.join(tempDir, 'test.sum');
await driveMany([], args: [
'--build-mode',
'--format=machine',
..._sdkSummaryArguments,
'--build-summary-only',
'--build-summary-output=$outputPath',
]);
var output = File(outputPath);
expect(output.existsSync(), isTrue);
expect(exitCode, 0);
});
}
Future<void> test_noStatistics() async {
await _doDrive(path.join('data', 'test_file.dart'));
// Should not print statistics summary.
expect(outSink.toString(), isEmpty);
expect(errorSink.toString(), isEmpty);
expect(exitCode, 0);
}
Future<void> test_onlyErrors_partFirst() async {
await withTempDirAsync((tempDir) async {
var aDart = path.join(tempDir, 'a.dart');
var bDart = path.join(tempDir, 'b.dart');
var aUri = 'package:aaa/a.dart';
var bUri = 'package:aaa/b.dart';
File(aDart).writeAsStringSync(r'''
library lib;
part 'b.dart';
class A {}
''');
File(bDart).writeAsStringSync('''
part of lib;
class B {}
var a = new A();
var b = new B();
''');
// Analyze b.dart (part) and then a.dart (its library).
// No errors should be reported - the part should know its library.
await _doDrive(bDart, fileUri: bUri, additionalArgs: ['$aUri|$aDart']);
expect(errorSink, isEmpty);
});
}
Future<void> test_packageConfig_packagesOptions() async {
await withTempDirAsync((tempDir) async {
var packagesPath = path.join(tempDir, 'aaa.packages');
var aaaRoot = path.join(tempDir, 'packages', 'aaa');
var aPath = path.join(aaaRoot, 'lib', 'a.dart');
var aUri = 'package:aaa/a.dart';
File(packagesPath).createSync(recursive: true);
File(packagesPath).writeAsStringSync('''
{
"configVersion": 2,
"packages": [
{
"name": "aaa",
"rootUri": "${path.toUri(aaaRoot)}",
"packageUri": "lib/",
"languageVersion": "2.4"
}
]
}
''');
File(aPath).createSync(recursive: true);
File(aPath).writeAsStringSync(r'''
extension E on int {}
''');
// Analyze package:aaa/a.dart and compute errors.
await _doDrive(
aPath,
fileUri: aUri,
additionalArgs: [
'--packages=$packagesPath',
],
);
expect(exitCode, ErrorSeverity.ERROR.ordinal);
expect(errorSink.toString(), contains('extension-methods'));
});
}
Future<void> test_packageConfig_relativeToFile() async {
await withTempDirAsync((tempDir) async {
var packagesPath = path.join(tempDir, '.dart_tool/package_config.json');
var aaaRoot = path.join(tempDir, 'packages', 'aaa');
var aPath = path.join(aaaRoot, 'lib', 'a.dart');
var aUri = 'package:aaa/a.dart';
File(packagesPath).createSync(recursive: true);
File(packagesPath).writeAsStringSync('''
{
"configVersion": 2,
"packages": [
{
"name": "aaa",
"rootUri": "${path.toUri(aaaRoot)}",
"packageUri": "lib/",
"languageVersion": "2.4"
}
]
}
''');
File(aPath).createSync(recursive: true);
File(aPath).writeAsStringSync(r'''
extension E on int {}
''');
// Analyze package:aaa/a.dart and compute errors.
await _doDrive(
aPath,
fileUri: aUri,
additionalArgs: [],
);
expect(exitCode, ErrorSeverity.ERROR.ordinal);
expect(errorSink.toString(), contains('extension-methods'));
});
}
Iterable<String> _linkedLibraryUnitUriList(
PackageBundleReader bundle,
String libraryUriStr,
) {
var libraries = bundle.libraries;
var library = libraries.singleWhere((l) => l.uriStr == libraryUriStr);
return library.units.map((u) => u.uriStr).toList();
}
Iterable<String> _linkedLibraryUriList(PackageBundleReader bundle) {
var libraries = bundle.libraries;
return libraries.map((l) => l.uriStr).toList();
}
}
@reflectiveTest
class ExitCodesTest extends BaseTest {
@SkippedTest(reason: 'Fails on bots, passes locally. Do not know why.')
Future<void> test_bazelWorkspace_relativePath() async {
// Copy to temp dir so that existing analysis options
// in the test directory hierarchy do not interfere
await withTempDirAsync((String tempDirPath) async {
var dartSdkPath = path.absolute(getSdkPath());
await recursiveCopy(
Directory(path.join(testDirectory, 'data', 'bazel')), tempDirPath);
var origWorkingDir = Directory.current;
try {
Directory.current = path.join(tempDirPath, 'proj');
var driver = Driver();
try {
await driver.start([
path.join('lib', 'file.dart'),
'--dart-sdk',
dartSdkPath,
]);
} catch (e) {
print('=== debug info ===');
print('dartSdkPath: $dartSdkPath');
print('stderr:\n${errorSink.toString()}');
rethrow;
}
expect(errorSink.toString(), isEmpty);
expect(outSink.toString(), contains('No issues found'));
expect(exitCode, 0);
} finally {
Directory.current = origWorkingDir;
}
});
}
Future<void> test_fatalErrors() async {
await drive('data/file_with_error.dart');
expect(exitCode, 3);
}
Future<void> test_fatalHints() async {
await drive('data/file_with_hint.dart', args: ['--fatal-hints']);
expect(exitCode, 1);
}
Future<void> test_missingDartFile() async {
await drive('data/NO_DART_FILE_HERE.dart');
expect(exitCode, 3);
}
Future<void> test_missingOptionsFile() async {
await drive('data/test_file.dart', options: 'data/NO_OPTIONS_HERE');
expect(exitCode, 3);
}
Future<void> test_notFatalHints() async {
await drive('data/file_with_hint.dart');
expect(exitCode, 0);
}
Future<void> test_partFile() async {
await driveMany([
path.join(testDirectory, 'data/library_and_parts/lib.dart'),
path.join(testDirectory, 'data/library_and_parts/part1.dart')
]);
expect(exitCode, 0);
}
Future<void> test_partFile_dangling() async {
await drive('data/library_and_parts/part2.dart');
expect(exitCode, 3);
}
Future<void> test_partFile_extra() async {
await driveMany([
path.join(testDirectory, 'data/library_and_parts/lib.dart'),
path.join(testDirectory, 'data/library_and_parts/part1.dart'),
path.join(testDirectory, 'data/library_and_parts/part2.dart')
]);
expect(exitCode, 3);
}
Future<void> test_partFile_reversed() async {
var driver = Driver();
await driver.start([
path.join(testDirectory, 'data/library_and_parts/part1.dart'),
path.join(testDirectory, 'data/library_and_parts/lib.dart')
]);
expect(exitCode, 0);
}
}
@reflectiveTest
class LinterTest extends BaseTest {
String get optionsFileName => AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE;
Future<void> test_containsLintRuleEntry() async {
var 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);
}
Future<void> test_defaultLints_generatedLints() async {
await _runLinter_defaultLints();
expect(bulletToDash(outSink),
contains('lint - Name types using UpperCamelCase'));
}
Future<void> test_defaultLints_getsDefaultLints() async {
await _runLinter_defaultLints();
/// Lints should be enabled.
expect(analysisOptions.lint, isTrue);
/// Default list should include camel_case_types.
var lintNames = analysisOptions.lintRules.map((r) => r.name);
expect(lintNames, contains('camel_case_types'));
}
Future<void> test_lintsInOptions_generatedLints() async {
await _runLinter_lintsInOptions();
expect(bulletToDash(outSink),
contains('lint - Name types using UpperCamelCase'));
}
Future<void> test_lintsInOptions_getAnalysisOptions() async {
await _runLinter_lintsInOptions();
/// Lints should be enabled.
expect(analysisOptions.lint, isTrue);
/// The analysis options file specifies 'camel_case_types' and 'sort_pub_dependencies'.
var lintNames = analysisOptions.lintRules.map((r) => r.name);
expect(lintNames,
orderedEquals(['camel_case_types', 'sort_pub_dependencies']));
}
Future<void> test_noLints_lintsDisabled() async {
await _runLinter_noLintsFlag();
expect(analysisOptions.lint, isFalse);
}
Future<void> test_noLints_noGeneratedWarnings() async {
await _runLinter_noLintsFlag();
expect(outSink.toString(), contains('No issues found'));
}
Future<void> test_noLints_noRegisteredLints() async {
await _runLinter_noLintsFlag();
expect(analysisOptions.lintRules, isEmpty);
}
Future<void> test_pubspec_lintsInOptions_generatedLints() async {
await drive('data/linter_project/pubspec.yaml',
options: 'data/linter_project/$optionsFileName');
expect(bulletToDash(outSink), contains('lint - Sort pub dependencies'));
}
YamlMap _parseOptions(String src) =>
AnalysisOptionsProvider().getOptionsFromString(src);
Future<void> _runLinter_defaultLints() async {
await drive('data/linter_project/test_file.dart',
options: 'data/linter_project/$optionsFileName', args: ['--lints']);
}
Future<void> _runLinter_lintsInOptions() async {
await drive('data/linter_project/test_file.dart',
options: 'data/linter_project/$optionsFileName', args: ['--lints']);
}
Future<void> _runLinter_noLintsFlag() async {
await drive('data/no_lints_project/test_file.dart',
options: 'data/no_lints_project/$optionsFileName');
}
}
@reflectiveTest
class NonDartFilesTest extends BaseTest {
Future<void> test_analysisOptionsYaml() async {
await withTempDirAsync((tempDir) async {
var filePath =
path.join(tempDir, AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE);
File(filePath).writeAsStringSync('''
analyzer:
string-mode: true
''');
await drive(filePath);
expect(
bulletToDash(outSink),
contains(
"warning - The option 'string-mode' isn't supported by 'analyzer'"));
expect(exitCode, 0);
});
}
Future<void> test_manifestFileChecks() async {
await withTempDirAsync((tempDir) async {
var filePath =
path.join(tempDir, AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE);
File(filePath).writeAsStringSync('''
analyzer:
optional-checks:
chrome-os-manifest-checks: true
''');
var manifestPath =
path.join(tempDir, AnalysisEngine.ANDROID_MANIFEST_FILE);
File(manifestPath).writeAsStringSync('''
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-feature android:name="android.software.home_screen" />
</manifest>
''');
await drive(manifestPath, options: filePath);
expect(
bulletToDash(outSink),
contains(
'warning - The feature android.software.home_screen is not supported on Chrome OS'));
expect(exitCode, 0);
});
}
Future<void> test_pubspecYaml() async {
await withTempDirAsync((tempDir) async {
var filePath = path.join(tempDir, AnalysisEngine.PUBSPEC_YAML_FILE);
File(filePath).writeAsStringSync('''
name: foo
flutter:
assets:
doesNotExist.gif
''');
await drive(filePath);
expect(
bulletToDash(outSink),
contains(
"warning - The value of the 'asset' field is expected to be a list of relative file paths"));
expect(exitCode, 0);
});
}
}
@reflectiveTest
class OptionsTest extends BaseTest {
String get optionsFileName => AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE;
List<ErrorProcessor> get processors => analysisOptions.errorProcessors;
ErrorProcessor processorFor(AnalysisError error) =>
processors.firstWhere((p) => p.appliesTo(error));
Future<void> test_analysisOptions_excludes() async {
await drive('data/exclude_test_project',
options: 'data/exclude_test_project/$optionsFileName');
_expectUndefinedClassErrorsWithoutExclusions();
}
Future<void>
test_analysisOptions_excludesRelativeToAnalysisOptions_explicit() async {
// The exclude is relative to the project, not/ the analyzed path, and it
// has to then understand that.
await drive('data/exclude_test_project',
options: 'data/exclude_test_project/$optionsFileName');
_expectUndefinedClassErrorsWithoutExclusions();
}
Future<void>
test_analysisOptions_excludesRelativeToAnalysisOptions_inferred() async {
// By passing no options, and the path `lib`, it should discover the
// analysis_options above lib. The exclude is relative to the project, not
// the analyzed path, and it has to then understand that.
await drive('data/exclude_test_project/lib', options: null);
_expectUndefinedClassErrorsWithoutExclusions();
}
Future<void> test_analyzeFilesInDifferentContexts() async {
await driveMany([
'data/linter_project/test_file.dart',
'data/no_lints_project/test_file.dart',
], options: null);
// Should have the lint in the project with lint rules enabled.
expect(
bulletToDash(outSink),
contains(path.join('linter_project', 'test_file.dart') +
':7:7 - camel_case_types'));
// Should be just one lint in total.
expect(outSink.toString(), contains('1 lint found.'));
}
Future<void> test_basic_filters() async {
await _driveBasic();
expect(processors, hasLength(3));
// unused_local_variable: ignore
var unused_local_variable =
AnalysisError(TestSource(), 0, 1, HintCode.UNUSED_LOCAL_VARIABLE, [
['x']
]);
expect(processorFor(unused_local_variable).severity, isNull);
// missing_return: error
var missing_return =
AnalysisError(TestSource(), 0, 1, HintCode.MISSING_RETURN, [
['x']
]);
expect(processorFor(missing_return).severity, ErrorSeverity.ERROR);
expect(bulletToDash(outSink),
contains("error - This function has a return type of 'int'"));
expect(outSink.toString(), contains('1 error and 1 warning found.'));
}
Future<void> test_includeDirective() async {
var testDir = path.join(
testDirectory, 'data', 'options_include_directive_tests_project');
await drive(
path.join(testDir, 'lib', 'test_file.dart'),
args: [
'--fatal-warnings',
'--packages',
path.join(testDir, '_packages'),
],
options: path.join(testDir, 'analysis_options.yaml'),
);
expect(exitCode, 3);
expect(outSink.toString(), contains('Unnecessary cast.'));
expect(outSink.toString(), contains('isn\'t defined'));
expect(outSink.toString(), contains('Avoid empty else statements'));
}
Future<void> test_todo() async {
await drive('data/file_with_todo.dart');
expect(outSink.toString().contains('[info]'), isFalse);
}
Future<void> test_withFlags_overrideFatalWarning() async {
await drive('data/options_tests_project/test_file.dart',
args: ['--fatal-warnings'],
options: 'data/options_tests_project/$optionsFileName');
// missing_return: error
var undefined_function = AnalysisError(
TestSource(), 0, 1, CompileTimeErrorCode.UNDEFINED_FUNCTION, [
['x']
]);
expect(processorFor(undefined_function).severity, ErrorSeverity.WARNING);
// Should not be made fatal by `--fatal-warnings`.
expect(bulletToDash(outSink),
contains("warning - The function 'baz' isn't defined"));
expect(outSink.toString(), contains('1 error and 1 warning found.'));
}
Future<void> _driveBasic() async {
await drive('data/options_tests_project/test_file.dart',
options: 'data/options_tests_project/$optionsFileName');
}
void _expectUndefinedClassErrorsWithoutExclusions() {
expect(bulletToDash(outSink),
contains("error - Undefined class 'IncludedUndefinedClass'"));
expect(bulletToDash(outSink),
isNot(contains("error - Undefined class 'ExcludedUndefinedClass'")));
expect(outSink.toString(), contains('1 error found.'));
}
}
class TestSource implements Source {
TestSource();
@override
String get fullName => '/package/lib/test.dart';
@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
class _DependencyPackage {
final String name;
final String path;
final String uri;
final String sum;
final String dep;
_DependencyPackage({this.name, this.path, this.uri, this.sum, this.dep});
}