| // 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)); |
| }); |
| }); |
| } |