blob: 826283d9d81bf8570db8dfd69dbf64c629ef1866 [file] [log] [blame]
// Copyright (c) 2016, 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 'package:front_end/compiler_options.dart';
import 'package:front_end/dependency_grapher.dart';
import 'package:front_end/memory_file_system.dart';
import 'package:path/path.dart' as pathos;
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(DependencyGrapherTest);
});
}
@reflectiveTest
class DependencyGrapherTest {
LibraryNode checkLibrary(LibraryCycleNode cycle, String uri,
{List<String> dependencies: const ['dart:core'],
List<String> parts: const []}) {
var library = cycle.libraries[Uri.parse(uri)];
expect('${library.uri}', uri);
expect(library.dependencies.map((dep) => '${dep.uri}'),
unorderedEquals(dependencies));
expect(library.parts.map((part) => '$part'), unorderedEquals(parts));
return library;
}
Future<List<LibraryCycleNode>> getCycles(Map<String, String> contents,
{List<String> startingPoints, String packagesFilePath = ''}) async {
// If no starting points given, assume the first entry in [contents] is the
// single starting point.
startingPoints ??= [contents.keys.first];
var fileSystem = new MemoryFileSystem(pathos.posix, Uri.parse('file:///'));
contents.forEach((path, text) {
fileSystem.entityForUri(pathos.posix.toUri(path)).writeAsStringSync(text);
});
// TODO(paulberry): implement and test other option possibilities.
var options = new CompilerOptions()
..fileSystem = fileSystem
..chaseDependencies = true
..packagesFileUri = packagesFilePath == ''
? new Uri()
: pathos.posix.toUri(packagesFilePath);
var graph = await graphForProgram(
startingPoints.map(pathos.posix.toUri).toList(), options);
return graph.topologicallySortedCycles;
}
/// Sort the given library cycles into a deterministic order based on their
/// URIs for easier unit testing.
List<LibraryCycleNode> sortCycles(Iterable<LibraryCycleNode> cycles) {
var result = cycles.toList();
String sortKey(LibraryCycleNode node) => node.libraries.keys.join(',');
result.sort((a, b) => Comparable.compare(sortKey(a), sortKey(b)));
return result;
}
test_explicitCoreDependency() async {
// If "dart:core" is explicitly imported, there shouldn't be two imports of
// "dart:core", just one.
var cycles = await getCycles({'/foo.dart': 'import "dart:core";'});
expect(cycles, hasLength(1));
expect(cycles[0].libraries, hasLength(1));
checkLibrary(cycles[0], 'file:///foo.dart');
}
test_exportDependency() async {
var cycles =
await getCycles({'/foo.dart': 'export "bar.dart";', '/bar.dart': ''});
expect(cycles, hasLength(2));
expect(cycles[0].libraries, hasLength(1));
checkLibrary(cycles[0], 'file:///bar.dart');
expect(cycles[1].libraries, hasLength(1));
checkLibrary(cycles[1], 'file:///foo.dart',
dependencies: ['file:///bar.dart', 'dart:core']);
}
test_importDependency() async {
var cycles =
await getCycles({'/foo.dart': 'import "bar.dart";', '/bar.dart': ''});
expect(cycles, hasLength(2));
expect(cycles[0].libraries, hasLength(1));
checkLibrary(cycles[0], 'file:///bar.dart');
expect(cycles[1].libraries, hasLength(1));
checkLibrary(cycles[1], 'file:///foo.dart',
dependencies: ['file:///bar.dart', 'dart:core']);
}
test_multipleStartingPoints() async {
var cycles = await getCycles({
'/a.dart': 'import "c.dart";',
'/b.dart': 'import "c.dart";',
'/c.dart': ''
}, startingPoints: [
'/a.dart',
'/b.dart'
]);
expect(cycles, hasLength(3));
expect(cycles[0].libraries, hasLength(1));
checkLibrary(cycles[0], 'file:///c.dart');
// The other two cycles might be in any order, so sort them for
// reproducibility.
List<LibraryCycleNode> otherCycles = sortCycles(cycles.sublist(1));
checkLibrary(otherCycles[0], 'file:///a.dart',
dependencies: ['file:///c.dart', 'dart:core']);
checkLibrary(otherCycles[1], 'file:///b.dart',
dependencies: ['file:///c.dart', 'dart:core']);
}
test_packages() async {
var cycles = await getCycles({
'/foo.dart': 'import "package:foo/bar.dart";',
'/.packages': 'foo:pkg/foo/lib\nbar:pkg/bar/lib\n',
'/pkg/foo/lib/bar.dart': 'import "package:bar/baz.dart";',
'/pkg/bar/lib/baz.dart': ''
}, packagesFilePath: '/.packages');
expect(cycles, hasLength(3));
expect(cycles[0].libraries, hasLength(1));
checkLibrary(cycles[0], 'package:bar/baz.dart');
expect(cycles[1].libraries, hasLength(1));
checkLibrary(cycles[1], 'package:foo/bar.dart',
dependencies: ['package:bar/baz.dart', 'dart:core']);
expect(cycles[2].libraries, hasLength(1));
checkLibrary(cycles[2], 'file:///foo.dart',
dependencies: ['package:foo/bar.dart', 'dart:core']);
}
test_parts() async {
var cycles = await getCycles({
'/foo.dart': 'library foo; part "a.dart"; part "b.dart";',
'/a.dart': 'part of foo;',
'/b.dart': 'part of foo;'
});
expect(cycles, hasLength(1));
expect(cycles[0].libraries, hasLength(1));
checkLibrary(cycles[0], 'file:///foo.dart',
parts: ['file:///a.dart', 'file:///b.dart']);
}
test_relativeUris() async {
var cycles = await getCycles({
'/a.dart': 'import "b/c.dart";',
'/b/c.dart': 'import "d/e.dart";',
'/b/d/e.dart': 'import "../f.dart";',
'/b/f.dart': ''
});
expect(cycles, hasLength(4));
expect(cycles[0].libraries, hasLength(1));
checkLibrary(cycles[0], 'file:///b/f.dart');
expect(cycles[1].libraries, hasLength(1));
checkLibrary(cycles[1], 'file:///b/d/e.dart',
dependencies: ['file:///b/f.dart', 'dart:core']);
expect(cycles[2].libraries, hasLength(1));
checkLibrary(cycles[2], 'file:///b/c.dart',
dependencies: ['file:///b/d/e.dart', 'dart:core']);
expect(cycles[3].libraries, hasLength(1));
checkLibrary(cycles[3], 'file:///a.dart',
dependencies: ['file:///b/c.dart', 'dart:core']);
}
test_sdkDependency() async {
// Dependencies on the SDK should be recorded even if SDK libraries aren't
// being included in the graph.
var cycles = await getCycles({'/foo.dart': 'import "dart:async";'});
expect(cycles, hasLength(1));
expect(cycles[0].libraries, hasLength(1));
checkLibrary(cycles[0], 'file:///foo.dart',
dependencies: ['dart:core', 'dart:async']);
}
test_simpleCycle() async {
var cycles = await getCycles(
{'/foo.dart': 'import "bar.dart";', '/bar.dart': 'import "foo.dart";'});
expect(cycles, hasLength(1));
expect(cycles[0].libraries, hasLength(2));
var foo = checkLibrary(cycles[0], 'file:///foo.dart',
dependencies: ['file:///bar.dart', 'dart:core']);
var bar = checkLibrary(cycles[0], 'file:///bar.dart',
dependencies: ['file:///foo.dart', 'dart:core']);
expect(foo.dependencies[0], same(bar));
expect(bar.dependencies[0], same(foo));
}
test_singleFile() async {
var cycles = await getCycles({'/foo.dart': ''});
expect(cycles, hasLength(1));
expect(cycles[0].libraries, hasLength(1));
checkLibrary(cycles[0], 'file:///foo.dart');
}
}