blob: aea5b8b1456b7c0b2bdf8c832acf3122852d7371 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(https://fxbug.dev/84961): Fix null safety and remove this language version.
// @dart=2.9
import 'package:analyzer/dart/analysis/utilities.dart' as analyzer;
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:dart_strict_deps_lib/dependency_check.dart';
import 'package:dart_strict_deps_lib/exceptions.dart';
import 'package:mockito/mockito.dart';
import 'package:package_config/package_config.dart';
import 'package:quiver/core.dart';
import 'package:test/test.dart';
import 'package:tools.dart-strict-deps.dart_strict_deps_proto/protos/models.pb.dart';
class MockResolver extends Mock implements PackageConfig {}
class MockImportDirective extends Mock implements ImportDirective {}
class MockCompUnit extends Mock implements CompilationUnit {}
class MockLineInfo extends Mock implements LineInfo {}
void main() {
group('collectKnownLibsFromDirectDeps', () {
test('Collect Known Libs works on own target', () {
const String testTarget = 'testName';
BuildTarget selfTarget = BuildTarget()
..packageName = testTarget
..isCurrentTarget = true
..rebasedSources.add('a');
BuildInfo buildInfo = BuildInfo();
buildInfo.buildTargets.add(selfTarget);
final knownLibFiles =
collectKnownLibsFromDirectDeps(buildInfo: buildInfo);
expect(knownLibFiles, {'a'});
});
test('Collect Known Libs works on other target', () {
const String otherTargetName = 'otherName';
BuildTarget otherTarget = BuildTarget()
..packageName = otherTargetName
..isCurrentTarget = false
..rebasedSources.add('a');
BuildInfo buildInfo = BuildInfo();
buildInfo.buildTargets.add(otherTarget);
final knownLibFiles =
collectKnownLibsFromDirectDeps(buildInfo: buildInfo);
expect(knownLibFiles, {'a'});
});
});
group('Import path handling', () {
MockResolver mockResolver = MockResolver();
const currentFileDirectory = '/somepath/subfolder/';
final resolverContext = ResolverContext(mockResolver, {}, {},
currentFileDirectory: currentFileDirectory);
test('Import Directive ignores dart and dart-ext imports', () {
expect(isDartSDKImport(uri: Uri.parse('dart:io')), isTrue);
expect(isDartSDKImport(uri: Uri.parse('dart-ext:io')), isTrue);
expect(isDartSDKImport(uri: Uri.parse('package:io')), isFalse);
expect(isDartSDKImport(uri: Uri.parse('../somefile.txt')), isFalse);
});
test('Import path resolves file to uri', () {
Uri fileUri = Uri.parse('somefile.txt');
expect(
resolveImportPath(
uri: fileUri,
resolverContext: resolverContext,
type: ImportResult_Type.RELATIVE),
Optional.of('/somepath/subfolder/somefile.txt'));
});
test('Import path fails on invalid scheme', () {
Uri invalidUri = Uri.parse('nonValidScheme:something');
expect(
resolveImportPath(
uri: invalidUri,
resolverContext: resolverContext,
type: ImportResult_Type.ERROR),
Optional.absent());
});
test('Import path resolves relative path', () {
Uri relativePathUri = Uri.parse('../somefile.txt');
// tests for path normalization
expect(
resolveImportPath(
uri: relativePathUri,
resolverContext: resolverContext,
type: ImportResult_Type.RELATIVE),
Optional.of('/somepath/somefile.txt'));
});
test('Import path resolves correctUri', () {
Uri resolveablePackageUri = Uri.parse('package:resolveableUri');
Uri resolvedPackageUri = Uri.parse('resolvedUri');
when(mockResolver.resolve(resolveablePackageUri))
.thenReturn(resolvedPackageUri);
expect(
resolveImportPath(
uri: resolveablePackageUri,
resolverContext: resolverContext,
type: ImportResult_Type.PACKAGE),
Optional.of(resolvedPackageUri.path));
});
});
group('getImportType', () {
test('dart: import', () {
expect(getImportType(Uri.parse('dart:io')), ImportResult_Type.DART_SDK);
});
test('dart-ext: import', () {
expect(
getImportType(Uri.parse('dart-ext:io')), ImportResult_Type.DART_SDK);
});
test('Invalid scheme', () {
expect(getImportType(Uri.parse('ptato:io')), ImportResult_Type.ERROR);
});
test('package import', () {
expect(getImportType(Uri.parse('package:io')), ImportResult_Type.PACKAGE);
});
test('relative import', () {
expect(getImportType(Uri.parse('./io.txt')), ImportResult_Type.RELATIVE);
});
});
group('compilationUnit', () {
MockResolver resolver = MockResolver();
ResolverContext resolverContext = ResolverContext(resolver, {}, {});
const file = '/somedir/somefile.txt';
test('checkCompilationUnit dart imports found', () {
String sourceText = '''import 'dart:io';''';
final compUnit = analyzer.parseString(content: sourceText).unit;
FileCheckResult res = checkCompilationUnit(compUnit,
file: file, resolverContext: resolverContext);
// dart imports are found even with empty known lib files as we assume SDK imports are always valid
expect(res.imports, hasLength(1));
expect(res.imports[0].importUri, 'dart:io');
expect(res.imports[0].state, ImportResult_State.FOUND);
});
test('checkCompilationUnit package imports found', () {
String sourceText = '''import 'package:resolveableUri';''';
final compUnit = analyzer.parseString(content: sourceText).unit;
final knownLibFiles = {'resolvedUri'};
ResolverContext resolverContext =
ResolverContext(resolver, knownLibFiles, {'resolveableUri'});
when(resolver.resolve(any)).thenReturn(Uri.parse('resolvedUri'));
FileCheckResult res = checkCompilationUnit(compUnit,
resolverContext: resolverContext, file: file);
expect(res.imports, hasLength(1));
expect(res.imports[0].importUri, 'package:resolveableUri');
expect(res.imports[0].resolvedLocation, 'resolvedUri');
expect(res.imports[0].state, ImportResult_State.FOUND);
});
test('checkCompilationUnit package import not dependency', () {
String sourceText = '''import 'package:resolveableUri';''';
final compUnit = analyzer.parseString(content: sourceText).unit;
when(resolver.resolve(any)).thenReturn(Uri.parse('resolvedUri'));
FileCheckResult res = checkCompilationUnit(compUnit,
file: file, resolverContext: resolverContext);
expect(res.imports, hasLength(1));
expect(res.imports[0].importUri, 'package:resolveableUri');
expect(res.imports[0].resolvedLocation, 'resolvedUri');
expect(res.imports[0].state, ImportResult_State.NOT_FOUND);
});
test('checkCompilationUnit invalid scheme', () {
String sourceText = '''import 'invalidScheme:resolveableUri';''';
final compUnit = analyzer.parseString(content: sourceText).unit;
FileCheckResult res = checkCompilationUnit(compUnit,
file: file, resolverContext: resolverContext);
expect(res.imports, hasLength(1));
expect(res.imports[0].state, ImportResult_State.NOT_FOUND);
});
test('checkCompilationUnit relative import', () {
String sourceText = '''import 'file.txt';''';
final compUnit = analyzer.parseString(content: sourceText).unit;
final knownLibFiles = {'/somedir/file.txt'};
ResolverContext resolverContext =
ResolverContext(resolver, knownLibFiles, {'mockPackage'})
..currentPackage = 'mockPackage';
FileCheckResult res = checkCompilationUnit(compUnit,
file: file, resolverContext: resolverContext);
expect(res.imports, hasLength(1));
expect(res.imports[0].importUri, 'file.txt');
expect(res.imports[0].resolvedLocation, '/somedir/file.txt');
expect(res.imports[0].state, ImportResult_State.FOUND);
});
test('checkCompilationUnit multiple imports', () {
String sourceText = '''
import 'dart:io';
import 'package:resolveableUri';
''';
final compUnit = analyzer.parseString(content: sourceText).unit;
final knownLibFiles = {'resolvedUri'};
when(resolver.resolve(any)).thenReturn(Uri.parse('resolvedUri'));
final knownPackages = {'resolveableUri'};
ResolverContext resolverContext =
ResolverContext(resolver, knownLibFiles, knownPackages);
FileCheckResult res = checkCompilationUnit(compUnit,
file: file, resolverContext: resolverContext);
expect(res.imports, hasLength(2));
expect(res.imports[0].importUri, 'dart:io');
expect(res.imports[0].state, ImportResult_State.FOUND);
expect(res.imports[1].importUri, 'package:resolveableUri');
expect(res.imports[1].resolvedLocation, 'resolvedUri');
expect(res.imports[1].state, ImportResult_State.FOUND);
});
test('checkTarget null compUnit', () {
final target = BuildTarget()..rebasedSources.add('a');
const compUnit = null;
final result = checkTarget(
target: target,
resolverContext: resolverContext,
parseCompilationUnitFunc: (f) => Optional.fromNullable(compUnit));
expect(result.files, isEmpty);
});
test('checkTarget goes through all targets', () {
final target = BuildTarget()..rebasedSources.addAll(['a', 'b', 'c']);
final compUnit = analyzer.parseString(content: '').unit;
final result = checkTarget(
target: target,
resolverContext: resolverContext,
parseCompilationUnitFunc: (f) => Optional.of(compUnit));
expect(result.files, hasLength(3));
});
});
group('checkBuildInfo', () {
MockResolver resolver = MockResolver();
test('missing current target', () {
final buildInfo = BuildInfo();
expect(() => checkBuildInfo(buildInfo: buildInfo, resolver: resolver),
throwsA(TypeMatcher<StrictDepsFatalException>()));
});
test('multiple current target throws error', () {
final buildTarget1 = BuildTarget()..isCurrentTarget = true;
final buildTarget2 = BuildTarget()..isCurrentTarget = true;
final buildInfo = BuildInfo()
..buildTargets.addAll([buildTarget1, buildTarget2]);
expect(() => checkBuildInfo(buildInfo: buildInfo, resolver: resolver),
throwsStateError);
});
test('e2e test', () {
final buildTarget1 = BuildTarget()
..isCurrentTarget = true
..rebasedSources.addAll(['./a.dart'])
..packageName = 'curPack';
final buildTarget2 = BuildTarget()
..isCurrentTarget = false
..rebasedSources.addAll(['/c.dart', '/subdir/d.dart'])
..packageName = 'otherPack';
final buildTarget3 = BuildTarget()
..isCurrentTarget = false
..rebasedSources.addAll(['/e.dart', '/subdir/f.dart'])
..packageName = 'otherPack2';
String sourceText = '''
import 'package:otherPack/c.dart';
import 'package:otherPack2/subdir/f.dart';
''';
final compUnit = analyzer.parseString(content: sourceText).unit;
MockResolver resolver = MockResolver();
when(resolver.resolve(Uri.parse('package:otherPack/c.dart')))
.thenReturn(Uri.parse('/c.dart'));
when(resolver.resolve(Uri.parse('package:otherPack/subdir/d.dart')))
.thenReturn(Uri.parse('/subdir/d.dart'));
when(resolver.resolve(Uri.parse('package:otherPack2/e.dart')))
.thenReturn(Uri.parse('/e.dart'));
when(resolver.resolve(Uri.parse('package:otherPack2/subdir/f.dart')))
.thenReturn(Uri.parse('/subdir/f.dart'));
final buildInfo = BuildInfo()
..buildTargets.addAll([buildTarget1, buildTarget2, buildTarget3]);
final targetCheckResult = checkBuildInfo(
buildInfo: buildInfo,
resolver: resolver,
parseCompilationUnitFunc: (f) => Optional.of(compUnit));
expect(targetCheckResult.files, hasLength(1));
expect(
targetCheckResult.files[0].imports.where(
(importResult) => importResult.state != ImportResult_State.FOUND),
hasLength(0));
});
test('e2e test fail', () {
final buildTarget1 = BuildTarget()
..isCurrentTarget = true
..rebasedSources.addAll(['./a.dart'])
..packageName = 'curPack';
final buildTarget2 = BuildTarget()
..isCurrentTarget = false
..rebasedSources.addAll(['/c.dart', '/subdir/d.dart'])
..packageName = 'otherPack';
final buildTarget3 = BuildTarget()
..isCurrentTarget = false
..rebasedSources.addAll(['/e.dart', '/subdir/f.dart'])
..packageName = 'otherPack2';
String sourceText = '''
import 'package:otherPack/z.dart';
import 'package:otherPack2/subdir/f.dart';
''';
final compUnit = analyzer.parseString(content: sourceText).unit;
MockResolver resolver = MockResolver();
when(resolver.resolve(Uri.parse('package:otherPack/c.dart')))
.thenReturn(Uri.parse('/c.dart'));
when(resolver.resolve(Uri.parse('package:otherPack/subdir/d.dart')))
.thenReturn(Uri.parse('/subdir/d.dart'));
when(resolver.resolve(Uri.parse('package:otherPack2/e.dart')))
.thenReturn(Uri.parse('/e.dart'));
when(resolver.resolve(Uri.parse('package:otherPack2/subdir/f.dart')))
.thenReturn(Uri.parse('/subdir/f.dart'));
final buildInfo = BuildInfo()
..buildTargets.addAll([buildTarget1, buildTarget2, buildTarget3]);
final targetCheckResult = checkBuildInfo(
buildInfo: buildInfo,
resolver: resolver,
parseCompilationUnitFunc: (f) => Optional.of(compUnit));
expect(targetCheckResult.files, hasLength(1));
});
});
}